diff options
Diffstat (limited to 'services/java/com/android/server/AssetAtlasService.java')
-rw-r--r-- | services/java/com/android/server/AssetAtlasService.java | 735 |
1 files changed, 0 insertions, 735 deletions
diff --git a/services/java/com/android/server/AssetAtlasService.java b/services/java/com/android/server/AssetAtlasService.java deleted file mode 100644 index 26b4652..0000000 --- a/services/java/com/android/server/AssetAtlasService.java +++ /dev/null @@ -1,735 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.Atlas; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.drawable.Drawable; -import android.os.Environment; -import android.os.RemoteException; -import android.os.SystemProperties; -import android.util.Log; -import android.util.LongSparseArray; -import android.view.GraphicBuffer; -import android.view.IAssetAtlas; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * This service is responsible for packing preloaded bitmaps into a single - * atlas texture. The resulting texture can be shared across processes to - * reduce overall memory usage. - * - * @hide - */ -public class AssetAtlasService extends IAssetAtlas.Stub { - /** - * Name of the <code>AssetAtlasService</code>. - */ - public static final String ASSET_ATLAS_SERVICE = "assetatlas"; - - private static final String LOG_TAG = "Atlas"; - - // Turns debug logs on/off. Debug logs are kept to a minimum and should - // remain on to diagnose issues - private static final boolean DEBUG_ATLAS = true; - - // When set to true the content of the atlas will be saved to disk - // in /data/system/atlas.png. The shared GraphicBuffer may be empty - private static final boolean DEBUG_ATLAS_TEXTURE = false; - - // Minimum size in pixels to consider for the resulting texture - private static final int MIN_SIZE = 768; - // Maximum size in pixels to consider for the resulting texture - private static final int MAX_SIZE = 2048; - // Increment in number of pixels between size variants when looking - // for the best texture dimensions - private static final int STEP = 64; - - // This percentage of the total number of pixels represents the minimum - // number of pixels we want to be able to pack in the atlas - private static final float PACKING_THRESHOLD = 0.8f; - - // Defines the number of int fields used to represent a single entry - // in the atlas map. This number defines the size of the array returned - // by the getMap(). See the mAtlasMap field for more information - private static final int ATLAS_MAP_ENTRY_FIELD_COUNT = 4; - - // Specifies how our GraphicBuffer will be used. To get proper swizzling - // the buffer will be written to using OpenGL (from JNI) so we can leave - // the software flag set to "never" - private static final int GRAPHIC_BUFFER_USAGE = GraphicBuffer.USAGE_SW_READ_NEVER | - GraphicBuffer.USAGE_SW_WRITE_NEVER | GraphicBuffer.USAGE_HW_TEXTURE; - - // This boolean is set to true if an atlas was successfully - // computed and rendered - private final AtomicBoolean mAtlasReady = new AtomicBoolean(false); - - private final Context mContext; - - // Version name of the current build, used to identify changes to assets list - private final String mVersionName; - - // Holds the atlas' data. This buffer can be mapped to - // OpenGL using an EGLImage - private GraphicBuffer mBuffer; - - // Describes how bitmaps are placed in the atlas. Each bitmap is - // represented by several entries in the array: - // int0: SkBitmap*, the native bitmap object - // int1: x position - // int2: y position - // int3: rotated, 1 if the bitmap must be rotated, 0 otherwise - // NOTE: This will need to be handled differently to support 64 bit pointers - private int[] mAtlasMap; - - /** - * Creates a new service. Upon creating, the service will gather the list of - * assets to consider for packing into the atlas and spawn a new thread to - * start the packing work. - * - * @param context The context giving access to preloaded resources - */ - public AssetAtlasService(Context context) { - mContext = context; - mVersionName = queryVersionName(context); - - ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>(300); - int totalPixelCount = 0; - - // We only care about drawables that hold bitmaps - final Resources resources = context.getResources(); - final LongSparseArray<Drawable.ConstantState> drawables = resources.getPreloadedDrawables(); - - final int count = drawables.size(); - for (int i = 0; i < count; i++) { - final Bitmap bitmap = drawables.valueAt(i).getBitmap(); - if (bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888) { - bitmaps.add(bitmap); - totalPixelCount += bitmap.getWidth() * bitmap.getHeight(); - } - } - - // Our algorithms perform better when the bitmaps are first sorted - // The comparator will sort the bitmap by width first, then by height - Collections.sort(bitmaps, new Comparator<Bitmap>() { - @Override - public int compare(Bitmap b1, Bitmap b2) { - if (b1.getWidth() == b2.getWidth()) { - return b2.getHeight() - b1.getHeight(); - } - return b2.getWidth() - b1.getWidth(); - } - }); - - // Kick off the packing work on a worker thread - new Thread(new Renderer(bitmaps, totalPixelCount)).start(); - } - - /** - * Queries the version name stored in framework's AndroidManifest. - * The version name can be used to identify possible changes to - * framework resources. - * - * @see #getBuildIdentifier(String) - */ - private static String queryVersionName(Context context) { - try { - String packageName = context.getPackageName(); - PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); - return info.versionName; - } catch (PackageManager.NameNotFoundException e) { - Log.w(LOG_TAG, "Could not get package info", e); - } - return null; - } - - /** - * Callback invoked by the server thread to indicate we can now run - * 3rd party code. - */ - public void systemRunning() { - } - - /** - * The renderer does all the work: - */ - private class Renderer implements Runnable { - private final ArrayList<Bitmap> mBitmaps; - private final int mPixelCount; - - private int mNativeBitmap; - - // Used for debugging only - private Bitmap mAtlasBitmap; - - Renderer(ArrayList<Bitmap> bitmaps, int pixelCount) { - mBitmaps = bitmaps; - mPixelCount = pixelCount; - } - - /** - * 1. On first boot or after every update, brute-force through all the - * possible atlas configurations and look for the best one (maximimize - * number of packed assets and minimize texture size) - * a. If a best configuration was computed, write it out to disk for - * future use - * 2. Read best configuration from disk - * 3. Compute the packing using the best configuration - * 4. Allocate a GraphicBuffer - * 5. Render assets in the buffer - */ - @Override - public void run() { - Configuration config = chooseConfiguration(mBitmaps, mPixelCount, mVersionName); - if (DEBUG_ATLAS) Log.d(LOG_TAG, "Loaded configuration: " + config); - - if (config != null) { - mBuffer = GraphicBuffer.create(config.width, config.height, - PixelFormat.RGBA_8888, GRAPHIC_BUFFER_USAGE); - - if (mBuffer != null) { - Atlas atlas = new Atlas(config.type, config.width, config.height, config.flags); - if (renderAtlas(mBuffer, atlas, config.count)) { - mAtlasReady.set(true); - } - } - } - } - - /** - * Renders a list of bitmaps into the atlas. The position of each bitmap - * was decided by the packing algorithm and will be honored by this - * method. If need be this method will also rotate bitmaps. - * - * @param buffer The buffer to render the atlas entries into - * @param atlas The atlas to pack the bitmaps into - * @param packCount The number of bitmaps that will be packed in the atlas - * - * @return true if the atlas was rendered, false otherwise - */ - @SuppressWarnings("MismatchedReadAndWriteOfArray") - private boolean renderAtlas(GraphicBuffer buffer, Atlas atlas, int packCount) { - // Use a Source blend mode to improve performance, the target bitmap - // will be zero'd out so there's no need to waste time applying blending - final Paint paint = new Paint(); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); - - // We always render the atlas into a bitmap. This bitmap is then - // uploaded into the GraphicBuffer using OpenGL to swizzle the content - final Canvas canvas = acquireCanvas(buffer.getWidth(), buffer.getHeight()); - if (canvas == null) return false; - - final Atlas.Entry entry = new Atlas.Entry(); - - mAtlasMap = new int[packCount * ATLAS_MAP_ENTRY_FIELD_COUNT]; - int[] atlasMap = mAtlasMap; - int mapIndex = 0; - - boolean result = false; - try { - final long startRender = System.nanoTime(); - final int count = mBitmaps.size(); - - for (int i = 0; i < count; i++) { - final Bitmap bitmap = mBitmaps.get(i); - if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) { - // We have more bitmaps to pack than the current configuration - // says, we were most likely not able to detect a change in the - // list of preloaded drawables, abort and delete the configuration - if (mapIndex >= mAtlasMap.length) { - deleteDataFile(); - break; - } - - canvas.save(); - canvas.translate(entry.x, entry.y); - if (entry.rotated) { - canvas.translate(bitmap.getHeight(), 0.0f); - canvas.rotate(90.0f); - } - canvas.drawBitmap(bitmap, 0.0f, 0.0f, null); - canvas.restore(); - - atlasMap[mapIndex++] = bitmap.mNativeBitmap; - atlasMap[mapIndex++] = entry.x; - atlasMap[mapIndex++] = entry.y; - atlasMap[mapIndex++] = entry.rotated ? 1 : 0; - } - } - - final long endRender = System.nanoTime(); - if (mNativeBitmap != 0) { - result = nUploadAtlas(buffer, mNativeBitmap); - } - - final long endUpload = System.nanoTime(); - if (DEBUG_ATLAS) { - float renderDuration = (endRender - startRender) / 1000.0f / 1000.0f; - float uploadDuration = (endUpload - endRender) / 1000.0f / 1000.0f; - Log.d(LOG_TAG, String.format("Rendered atlas in %.2fms (%.2f+%.2fms)", - renderDuration + uploadDuration, renderDuration, uploadDuration)); - } - - } finally { - releaseCanvas(canvas); - } - - return result; - } - - /** - * Returns a Canvas for the specified buffer. If {@link #DEBUG_ATLAS_TEXTURE} - * is turned on, the returned Canvas will render into a local bitmap that - * will then be saved out to disk for debugging purposes. - * @param width - * @param height - */ - private Canvas acquireCanvas(int width, int height) { - if (DEBUG_ATLAS_TEXTURE) { - mAtlasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - return new Canvas(mAtlasBitmap); - } else { - Canvas canvas = new Canvas(); - mNativeBitmap = nAcquireAtlasCanvas(canvas, width, height); - return canvas; - } - } - - /** - * Releases the canvas used to render into the buffer. Calling this method - * will release any resource previously acquired. If {@link #DEBUG_ATLAS_TEXTURE} - * is turend on, calling this method will write the content of the atlas - * to disk in /data/system/atlas.png for debugging. - */ - private void releaseCanvas(Canvas canvas) { - if (DEBUG_ATLAS_TEXTURE) { - canvas.setBitmap(null); - - File systemDirectory = new File(Environment.getDataDirectory(), "system"); - File dataFile = new File(systemDirectory, "atlas.png"); - - try { - FileOutputStream out = new FileOutputStream(dataFile); - mAtlasBitmap.compress(Bitmap.CompressFormat.PNG, 100, out); - out.close(); - } catch (FileNotFoundException e) { - // Ignore - } catch (IOException e) { - // Ignore - } - - mAtlasBitmap.recycle(); - mAtlasBitmap = null; - } else { - nReleaseAtlasCanvas(canvas, mNativeBitmap); - } - } - } - - private static native int nAcquireAtlasCanvas(Canvas canvas, int width, int height); - private static native void nReleaseAtlasCanvas(Canvas canvas, int bitmap); - private static native boolean nUploadAtlas(GraphicBuffer buffer, int bitmap); - - @Override - public boolean isCompatible(int ppid) { - return ppid == android.os.Process.myPpid(); - } - - @Override - public GraphicBuffer getBuffer() throws RemoteException { - return mAtlasReady.get() ? mBuffer : null; - } - - @Override - public int[] getMap() throws RemoteException { - return mAtlasReady.get() ? mAtlasMap : null; - } - - /** - * Finds the best atlas configuration to pack the list of supplied bitmaps. - * This method takes advantage of multi-core systems by spawning a number - * of threads equal to the number of available cores. - */ - private static Configuration computeBestConfiguration( - ArrayList<Bitmap> bitmaps, int pixelCount) { - if (DEBUG_ATLAS) Log.d(LOG_TAG, "Computing best atlas configuration..."); - - long begin = System.nanoTime(); - List<WorkerResult> results = Collections.synchronizedList(new ArrayList<WorkerResult>()); - - // Don't bother with an extra thread if there's only one processor - int cpuCount = Runtime.getRuntime().availableProcessors(); - if (cpuCount == 1) { - new ComputeWorker(MIN_SIZE, MAX_SIZE, STEP, bitmaps, pixelCount, results, null).run(); - } else { - int start = MIN_SIZE; - int end = MAX_SIZE - (cpuCount - 1) * STEP; - int step = STEP * cpuCount; - - final CountDownLatch signal = new CountDownLatch(cpuCount); - - for (int i = 0; i < cpuCount; i++, start += STEP, end += STEP) { - ComputeWorker worker = new ComputeWorker(start, end, step, - bitmaps, pixelCount, results, signal); - new Thread(worker, "Atlas Worker #" + (i + 1)).start(); - } - - try { - signal.await(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Log.w(LOG_TAG, "Could not complete configuration computation"); - return null; - } - } - - // Maximize the number of packed bitmaps, minimize the texture size - Collections.sort(results, new Comparator<WorkerResult>() { - @Override - public int compare(WorkerResult r1, WorkerResult r2) { - int delta = r2.count - r1.count; - if (delta != 0) return delta; - return r1.width * r1.height - r2.width * r2.height; - } - }); - - if (DEBUG_ATLAS) { - float delay = (System.nanoTime() - begin) / 1000.0f / 1000.0f / 1000.0f; - Log.d(LOG_TAG, String.format("Found best atlas configuration in %.2fs", delay)); - } - - WorkerResult result = results.get(0); - return new Configuration(result.type, result.width, result.height, result.count); - } - - /** - * Returns the path to the file containing the best computed - * atlas configuration. - */ - private static File getDataFile() { - File systemDirectory = new File(Environment.getDataDirectory(), "system"); - return new File(systemDirectory, "framework_atlas.config"); - } - - private static void deleteDataFile() { - Log.w(LOG_TAG, "Current configuration inconsistent with assets list"); - if (!getDataFile().delete()) { - Log.w(LOG_TAG, "Could not delete the current configuration"); - } - } - - private File getFrameworkResourcesFile() { - return new File(mContext.getApplicationInfo().sourceDir); - } - - /** - * Returns the best known atlas configuration. This method will either - * read the configuration from disk or start a brute-force search - * and save the result out to disk. - */ - private Configuration chooseConfiguration(ArrayList<Bitmap> bitmaps, int pixelCount, - String versionName) { - Configuration config = null; - - final File dataFile = getDataFile(); - if (dataFile.exists()) { - config = readConfiguration(dataFile, versionName); - } - - if (config == null) { - config = computeBestConfiguration(bitmaps, pixelCount); - if (config != null) writeConfiguration(config, dataFile, versionName); - } - - return config; - } - - /** - * Writes the specified atlas configuration to the specified file. - */ - private void writeConfiguration(Configuration config, File file, String versionName) { - BufferedWriter writer = null; - try { - writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); - writer.write(getBuildIdentifier(versionName)); - writer.newLine(); - writer.write(config.type.toString()); - writer.newLine(); - writer.write(String.valueOf(config.width)); - writer.newLine(); - writer.write(String.valueOf(config.height)); - writer.newLine(); - writer.write(String.valueOf(config.count)); - writer.newLine(); - writer.write(String.valueOf(config.flags)); - writer.newLine(); - } catch (FileNotFoundException e) { - Log.w(LOG_TAG, "Could not write " + file, e); - } catch (IOException e) { - Log.w(LOG_TAG, "Could not write " + file, e); - } finally { - if (writer != null) { - try { - writer.close(); - } catch (IOException e) { - // Ignore - } - } - } - } - - /** - * Reads an atlas configuration from the specified file. This method - * returns null if an error occurs or if the configuration is invalid. - */ - private Configuration readConfiguration(File file, String versionName) { - BufferedReader reader = null; - Configuration config = null; - try { - reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); - - if (checkBuildIdentifier(reader, versionName)) { - Atlas.Type type = Atlas.Type.valueOf(reader.readLine()); - int width = readInt(reader, MIN_SIZE, MAX_SIZE); - int height = readInt(reader, MIN_SIZE, MAX_SIZE); - int count = readInt(reader, 0, Integer.MAX_VALUE); - int flags = readInt(reader, Integer.MIN_VALUE, Integer.MAX_VALUE); - - config = new Configuration(type, width, height, count, flags); - } - } catch (IllegalArgumentException e) { - Log.w(LOG_TAG, "Invalid parameter value in " + file, e); - } catch (FileNotFoundException e) { - Log.w(LOG_TAG, "Could not read " + file, e); - } catch (IOException e) { - Log.w(LOG_TAG, "Could not read " + file, e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - // Ignore - } - } - } - return config; - } - - private static int readInt(BufferedReader reader, int min, int max) throws IOException { - return Math.max(min, Math.min(max, Integer.parseInt(reader.readLine()))); - } - - /** - * Compares the next line in the specified buffered reader to the current - * build identifier. Returns whether the two values are equal. - * - * @see #getBuildIdentifier(String) - */ - private boolean checkBuildIdentifier(BufferedReader reader, String versionName) - throws IOException { - String deviceBuildId = getBuildIdentifier(versionName); - String buildId = reader.readLine(); - return deviceBuildId.equals(buildId); - } - - /** - * Returns an identifier for the current build that can be used to detect - * likely changes to framework resources. The build identifier is made of - * several distinct values: - * - * build fingerprint/framework version name/file size of framework resources apk - * - * Only the build fingerprint should be necessary on user builds but - * the other values are useful to detect changes on eng builds during - * development. - * - * This identifier does not attempt to be exact: a new identifier does not - * necessarily mean the preloaded drawables have changed. It is important - * however that whenever the list of preloaded drawables changes, this - * identifier changes as well. - * - * @see #checkBuildIdentifier(java.io.BufferedReader, String) - */ - private String getBuildIdentifier(String versionName) { - return SystemProperties.get("ro.build.fingerprint", "") + '/' + versionName + '/' + - String.valueOf(getFrameworkResourcesFile().length()); - } - - /** - * Atlas configuration. Specifies the algorithm, dimensions and flags to use. - */ - private static class Configuration { - final Atlas.Type type; - final int width; - final int height; - final int count; - final int flags; - - Configuration(Atlas.Type type, int width, int height, int count) { - this(type, width, height, count, Atlas.FLAG_DEFAULTS); - } - - Configuration(Atlas.Type type, int width, int height, int count, int flags) { - this.type = type; - this.width = width; - this.height = height; - this.count = count; - this.flags = flags; - } - - @Override - public String toString() { - return type.toString() + " (" + width + "x" + height + ") flags=0x" + - Integer.toHexString(flags) + " count=" + count; - } - } - - /** - * Used during the brute-force search to gather information about each - * variant of the packing algorithm. - */ - private static class WorkerResult { - Atlas.Type type; - int width; - int height; - int count; - - WorkerResult(Atlas.Type type, int width, int height, int count) { - this.type = type; - this.width = width; - this.height = height; - this.count = count; - } - - @Override - public String toString() { - return String.format("%s %dx%d", type.toString(), width, height); - } - } - - /** - * A compute worker will try a finite number of variations of the packing - * algorithms and save the results in a supplied list. - */ - private static class ComputeWorker implements Runnable { - private final int mStart; - private final int mEnd; - private final int mStep; - private final List<Bitmap> mBitmaps; - private final List<WorkerResult> mResults; - private final CountDownLatch mSignal; - private final int mThreshold; - - /** - * Creates a new compute worker to brute-force through a range of - * packing algorithms variants. - * - * @param start The minimum texture width to try - * @param end The maximum texture width to try - * @param step The number of pixels to increment the texture width by at each step - * @param bitmaps The list of bitmaps to pack in the atlas - * @param pixelCount The total number of pixels occupied by the list of bitmaps - * @param results The list of results in which to save the brute-force search results - * @param signal Latch to decrement when this worker is done, may be null - */ - ComputeWorker(int start, int end, int step, List<Bitmap> bitmaps, int pixelCount, - List<WorkerResult> results, CountDownLatch signal) { - mStart = start; - mEnd = end; - mStep = step; - mBitmaps = bitmaps; - mResults = results; - mSignal = signal; - - // Minimum number of pixels we want to be able to pack - int threshold = (int) (pixelCount * PACKING_THRESHOLD); - // Make sure we can find at least one configuration - while (threshold > MAX_SIZE * MAX_SIZE) { - threshold >>= 1; - } - mThreshold = threshold; - } - - @Override - public void run() { - if (DEBUG_ATLAS) Log.d(LOG_TAG, "Running " + Thread.currentThread().getName()); - - Atlas.Entry entry = new Atlas.Entry(); - for (Atlas.Type type : Atlas.Type.values()) { - for (int width = mStart; width < mEnd; width += mStep) { - for (int height = MIN_SIZE; height < MAX_SIZE; height += STEP) { - // If the atlas is not big enough, skip it - if (width * height <= mThreshold) continue; - - final int count = packBitmaps(type, width, height, entry); - if (count > 0) { - mResults.add(new WorkerResult(type, width, height, count)); - // If we were able to pack everything let's stop here - // Increasing the height further won't make things better - if (count == mBitmaps.size()) { - break; - } - } - } - } - } - - if (mSignal != null) { - mSignal.countDown(); - } - } - - private int packBitmaps(Atlas.Type type, int width, int height, Atlas.Entry entry) { - int total = 0; - Atlas atlas = new Atlas(type, width, height); - - final int count = mBitmaps.size(); - for (int i = 0; i < count; i++) { - final Bitmap bitmap = mBitmaps.get(i); - if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) { - total++; - } - } - - return total; - } - } -} |