blob: 66cc29ad3795b41795e73131369bba27e78d4628 [file] [log] [blame]
Romain Guy3b748a42013-04-17 18:54:38 -07001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
19import android.content.Context;
20import android.content.pm.PackageInfo;
21import android.content.pm.PackageManager;
22import android.content.res.Resources;
23import android.graphics.Atlas;
24import android.graphics.Bitmap;
25import android.graphics.Canvas;
26import android.graphics.Paint;
27import android.graphics.PixelFormat;
28import android.graphics.PorterDuff;
29import android.graphics.PorterDuffXfermode;
30import android.graphics.drawable.Drawable;
31import android.os.Environment;
32import android.os.RemoteException;
33import android.os.SystemProperties;
34import android.util.Log;
35import android.util.LongSparseArray;
36import android.view.GraphicBuffer;
37import android.view.IAssetAtlas;
38
39import java.io.BufferedReader;
40import java.io.BufferedWriter;
41import java.io.File;
42import java.io.FileInputStream;
43import java.io.FileNotFoundException;
44import java.io.FileOutputStream;
45import java.io.IOException;
46import java.io.InputStreamReader;
47import java.io.OutputStreamWriter;
48import java.util.ArrayList;
John Reckdad7d84c2014-12-09 12:33:26 -080049import java.util.Collection;
Romain Guy3b748a42013-04-17 18:54:38 -070050import java.util.Collections;
51import java.util.Comparator;
John Reckdad7d84c2014-12-09 12:33:26 -080052import java.util.HashSet;
Romain Guy3b748a42013-04-17 18:54:38 -070053import java.util.List;
54import java.util.concurrent.CountDownLatch;
55import java.util.concurrent.TimeUnit;
56import java.util.concurrent.atomic.AtomicBoolean;
57
58/**
59 * This service is responsible for packing preloaded bitmaps into a single
60 * atlas texture. The resulting texture can be shared across processes to
61 * reduce overall memory usage.
62 *
63 * @hide
64 */
65public class AssetAtlasService extends IAssetAtlas.Stub {
66 /**
67 * Name of the <code>AssetAtlasService</code>.
68 */
69 public static final String ASSET_ATLAS_SERVICE = "assetatlas";
70
John Reckdad7d84c2014-12-09 12:33:26 -080071 private static final String LOG_TAG = "AssetAtlas";
Romain Guy3b748a42013-04-17 18:54:38 -070072
73 // Turns debug logs on/off. Debug logs are kept to a minimum and should
74 // remain on to diagnose issues
75 private static final boolean DEBUG_ATLAS = true;
76
77 // When set to true the content of the atlas will be saved to disk
78 // in /data/system/atlas.png. The shared GraphicBuffer may be empty
79 private static final boolean DEBUG_ATLAS_TEXTURE = false;
80
81 // Minimum size in pixels to consider for the resulting texture
82 private static final int MIN_SIZE = 768;
83 // Maximum size in pixels to consider for the resulting texture
84 private static final int MAX_SIZE = 2048;
85 // Increment in number of pixels between size variants when looking
86 // for the best texture dimensions
87 private static final int STEP = 64;
88
89 // This percentage of the total number of pixels represents the minimum
90 // number of pixels we want to be able to pack in the atlas
91 private static final float PACKING_THRESHOLD = 0.8f;
92
93 // Defines the number of int fields used to represent a single entry
94 // in the atlas map. This number defines the size of the array returned
95 // by the getMap(). See the mAtlasMap field for more information
96 private static final int ATLAS_MAP_ENTRY_FIELD_COUNT = 4;
97
98 // Specifies how our GraphicBuffer will be used. To get proper swizzling
99 // the buffer will be written to using OpenGL (from JNI) so we can leave
100 // the software flag set to "never"
101 private static final int GRAPHIC_BUFFER_USAGE = GraphicBuffer.USAGE_SW_READ_NEVER |
102 GraphicBuffer.USAGE_SW_WRITE_NEVER | GraphicBuffer.USAGE_HW_TEXTURE;
103
104 // This boolean is set to true if an atlas was successfully
105 // computed and rendered
106 private final AtomicBoolean mAtlasReady = new AtomicBoolean(false);
107
108 private final Context mContext;
109
110 // Version name of the current build, used to identify changes to assets list
111 private final String mVersionName;
112
113 // Holds the atlas' data. This buffer can be mapped to
114 // OpenGL using an EGLImage
115 private GraphicBuffer mBuffer;
116
117 // Describes how bitmaps are placed in the atlas. Each bitmap is
118 // represented by several entries in the array:
Ashok Bhat17ab38f2014-01-27 16:00:23 +0000119 // long0: SkBitmap*, the native bitmap object
120 // long1: x position
121 // long2: y position
122 // long3: rotated, 1 if the bitmap must be rotated, 0 otherwise
123 private long[] mAtlasMap;
Romain Guy3b748a42013-04-17 18:54:38 -0700124
125 /**
126 * Creates a new service. Upon creating, the service will gather the list of
127 * assets to consider for packing into the atlas and spawn a new thread to
128 * start the packing work.
129 *
130 * @param context The context giving access to preloaded resources
131 */
132 public AssetAtlasService(Context context) {
133 mContext = context;
134 mVersionName = queryVersionName(context);
135
John Reckdad7d84c2014-12-09 12:33:26 -0800136 Collection<Bitmap> bitmaps = new HashSet<Bitmap>(300);
Romain Guy3b748a42013-04-17 18:54:38 -0700137 int totalPixelCount = 0;
138
139 // We only care about drawables that hold bitmaps
140 final Resources resources = context.getResources();
141 final LongSparseArray<Drawable.ConstantState> drawables = resources.getPreloadedDrawables();
142
143 final int count = drawables.size();
144 for (int i = 0; i < count; i++) {
John Reckdad7d84c2014-12-09 12:33:26 -0800145 try {
146 totalPixelCount += drawables.valueAt(i).addAtlasableBitmaps(bitmaps);
147 } catch (Throwable t) {
148 Log.e("AssetAtlas", "Failed to fetch preloaded drawable state", t);
149 throw t;
Romain Guy3b748a42013-04-17 18:54:38 -0700150 }
151 }
152
John Reckdad7d84c2014-12-09 12:33:26 -0800153 ArrayList<Bitmap> sortedBitmaps = new ArrayList<Bitmap>(bitmaps);
Romain Guy3b748a42013-04-17 18:54:38 -0700154 // Our algorithms perform better when the bitmaps are first sorted
155 // The comparator will sort the bitmap by width first, then by height
John Reckdad7d84c2014-12-09 12:33:26 -0800156 Collections.sort(sortedBitmaps, new Comparator<Bitmap>() {
Romain Guy3b748a42013-04-17 18:54:38 -0700157 @Override
158 public int compare(Bitmap b1, Bitmap b2) {
159 if (b1.getWidth() == b2.getWidth()) {
160 return b2.getHeight() - b1.getHeight();
161 }
162 return b2.getWidth() - b1.getWidth();
163 }
164 });
165
166 // Kick off the packing work on a worker thread
John Reckdad7d84c2014-12-09 12:33:26 -0800167 new Thread(new Renderer(sortedBitmaps, totalPixelCount)).start();
Romain Guy3b748a42013-04-17 18:54:38 -0700168 }
169
170 /**
171 * Queries the version name stored in framework's AndroidManifest.
172 * The version name can be used to identify possible changes to
173 * framework resources.
174 *
175 * @see #getBuildIdentifier(String)
176 */
177 private static String queryVersionName(Context context) {
178 try {
179 String packageName = context.getPackageName();
180 PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
181 return info.versionName;
182 } catch (PackageManager.NameNotFoundException e) {
183 Log.w(LOG_TAG, "Could not get package info", e);
184 }
185 return null;
186 }
187
188 /**
189 * Callback invoked by the server thread to indicate we can now run
190 * 3rd party code.
191 */
Svetoslav Ganova0027152013-06-25 14:59:53 -0700192 public void systemRunning() {
Romain Guy3b748a42013-04-17 18:54:38 -0700193 }
194
195 /**
196 * The renderer does all the work:
197 */
198 private class Renderer implements Runnable {
199 private final ArrayList<Bitmap> mBitmaps;
200 private final int mPixelCount;
201
John Reck9d4efdf2015-04-17 20:45:40 +0000202 private long mNativeBitmap;
203
204 // Used for debugging only
John Reck7809f832015-04-17 20:45:19 +0000205 private Bitmap mAtlasBitmap;
206
Romain Guy3b748a42013-04-17 18:54:38 -0700207 Renderer(ArrayList<Bitmap> bitmaps, int pixelCount) {
208 mBitmaps = bitmaps;
209 mPixelCount = pixelCount;
210 }
211
212 /**
213 * 1. On first boot or after every update, brute-force through all the
214 * possible atlas configurations and look for the best one (maximimize
215 * number of packed assets and minimize texture size)
216 * a. If a best configuration was computed, write it out to disk for
217 * future use
218 * 2. Read best configuration from disk
219 * 3. Compute the packing using the best configuration
220 * 4. Allocate a GraphicBuffer
221 * 5. Render assets in the buffer
222 */
223 @Override
224 public void run() {
225 Configuration config = chooseConfiguration(mBitmaps, mPixelCount, mVersionName);
226 if (DEBUG_ATLAS) Log.d(LOG_TAG, "Loaded configuration: " + config);
227
228 if (config != null) {
229 mBuffer = GraphicBuffer.create(config.width, config.height,
230 PixelFormat.RGBA_8888, GRAPHIC_BUFFER_USAGE);
231
232 if (mBuffer != null) {
233 Atlas atlas = new Atlas(config.type, config.width, config.height, config.flags);
234 if (renderAtlas(mBuffer, atlas, config.count)) {
235 mAtlasReady.set(true);
236 }
237 }
238 }
239 }
240
241 /**
242 * Renders a list of bitmaps into the atlas. The position of each bitmap
243 * was decided by the packing algorithm and will be honored by this
244 * method. If need be this method will also rotate bitmaps.
245 *
246 * @param buffer The buffer to render the atlas entries into
247 * @param atlas The atlas to pack the bitmaps into
248 * @param packCount The number of bitmaps that will be packed in the atlas
249 *
250 * @return true if the atlas was rendered, false otherwise
251 */
252 @SuppressWarnings("MismatchedReadAndWriteOfArray")
253 private boolean renderAtlas(GraphicBuffer buffer, Atlas atlas, int packCount) {
254 // Use a Source blend mode to improve performance, the target bitmap
255 // will be zero'd out so there's no need to waste time applying blending
256 final Paint paint = new Paint();
257 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
258
259 // We always render the atlas into a bitmap. This bitmap is then
260 // uploaded into the GraphicBuffer using OpenGL to swizzle the content
John Reck7809f832015-04-17 20:45:19 +0000261 final Canvas canvas = acquireCanvas(buffer.getWidth(), buffer.getHeight());
262 if (canvas == null) return false;
Romain Guy3b748a42013-04-17 18:54:38 -0700263
264 final Atlas.Entry entry = new Atlas.Entry();
265
Ashok Bhat17ab38f2014-01-27 16:00:23 +0000266 mAtlasMap = new long[packCount * ATLAS_MAP_ENTRY_FIELD_COUNT];
267 long[] atlasMap = mAtlasMap;
Romain Guy3b748a42013-04-17 18:54:38 -0700268 int mapIndex = 0;
269
270 boolean result = false;
John Reck7809f832015-04-17 20:45:19 +0000271 try {
272 final long startRender = System.nanoTime();
273 final int count = mBitmaps.size();
Romain Guy3b748a42013-04-17 18:54:38 -0700274
John Reck7809f832015-04-17 20:45:19 +0000275 for (int i = 0; i < count; i++) {
276 final Bitmap bitmap = mBitmaps.get(i);
277 if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) {
278 // We have more bitmaps to pack than the current configuration
279 // says, we were most likely not able to detect a change in the
280 // list of preloaded drawables, abort and delete the configuration
281 if (mapIndex >= mAtlasMap.length) {
282 deleteDataFile();
283 break;
284 }
John Reck87ffb632015-04-15 13:24:47 -0700285
John Reck7809f832015-04-17 20:45:19 +0000286 canvas.save();
287 canvas.translate(entry.x, entry.y);
288 if (entry.rotated) {
289 canvas.translate(bitmap.getHeight(), 0.0f);
290 canvas.rotate(90.0f);
291 }
292 canvas.drawBitmap(bitmap, 0.0f, 0.0f, null);
293 canvas.restore();
294 atlasMap[mapIndex++] = bitmap.getSkBitmap();
295 atlasMap[mapIndex++] = entry.x;
296 atlasMap[mapIndex++] = entry.y;
297 atlasMap[mapIndex++] = entry.rotated ? 1 : 0;
John Reck87ffb632015-04-15 13:24:47 -0700298 }
Romain Guy3b748a42013-04-17 18:54:38 -0700299 }
300
John Reck7809f832015-04-17 20:45:19 +0000301 final long endRender = System.nanoTime();
John Reck9d4efdf2015-04-17 20:45:40 +0000302 if (mNativeBitmap != 0) {
303 result = nUploadAtlas(buffer, mNativeBitmap);
304 }
Romain Guy3b748a42013-04-17 18:54:38 -0700305
John Reck7809f832015-04-17 20:45:19 +0000306 final long endUpload = System.nanoTime();
307 if (DEBUG_ATLAS) {
308 float renderDuration = (endRender - startRender) / 1000.0f / 1000.0f;
309 float uploadDuration = (endUpload - endRender) / 1000.0f / 1000.0f;
310 Log.d(LOG_TAG, String.format("Rendered atlas in %.2fms (%.2f+%.2fms)",
311 renderDuration + uploadDuration, renderDuration, uploadDuration));
312 }
313
314 } finally {
315 releaseCanvas(canvas);
Romain Guy3b748a42013-04-17 18:54:38 -0700316 }
317
318 return result;
319 }
320
321 /**
John Reck7809f832015-04-17 20:45:19 +0000322 * Returns a Canvas for the specified buffer. If {@link #DEBUG_ATLAS_TEXTURE}
323 * is turned on, the returned Canvas will render into a local bitmap that
324 * will then be saved out to disk for debugging purposes.
325 * @param width
326 * @param height
327 */
328 private Canvas acquireCanvas(int width, int height) {
John Reck9d4efdf2015-04-17 20:45:40 +0000329 if (DEBUG_ATLAS_TEXTURE) {
330 mAtlasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
331 return new Canvas(mAtlasBitmap);
332 } else {
333 Canvas canvas = new Canvas();
334 mNativeBitmap = nAcquireAtlasCanvas(canvas, width, height);
335 return canvas;
336 }
John Reck7809f832015-04-17 20:45:19 +0000337 }
338
339 /**
Romain Guy3b748a42013-04-17 18:54:38 -0700340 * Releases the canvas used to render into the buffer. Calling this method
341 * will release any resource previously acquired. If {@link #DEBUG_ATLAS_TEXTURE}
342 * is turend on, calling this method will write the content of the atlas
343 * to disk in /data/system/atlas.png for debugging.
344 */
John Reck7809f832015-04-17 20:45:19 +0000345 private void releaseCanvas(Canvas canvas) {
Romain Guy3b748a42013-04-17 18:54:38 -0700346 if (DEBUG_ATLAS_TEXTURE) {
John Reck9d4efdf2015-04-17 20:45:40 +0000347 canvas.setBitmap(null);
Romain Guy3b748a42013-04-17 18:54:38 -0700348
349 File systemDirectory = new File(Environment.getDataDirectory(), "system");
350 File dataFile = new File(systemDirectory, "atlas.png");
351
352 try {
353 FileOutputStream out = new FileOutputStream(dataFile);
John Reck7809f832015-04-17 20:45:19 +0000354 mAtlasBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
Romain Guy3b748a42013-04-17 18:54:38 -0700355 out.close();
356 } catch (FileNotFoundException e) {
357 // Ignore
358 } catch (IOException e) {
359 // Ignore
360 }
John Reck9d4efdf2015-04-17 20:45:40 +0000361
362 mAtlasBitmap.recycle();
363 mAtlasBitmap = null;
364 } else {
365 nReleaseAtlasCanvas(canvas, mNativeBitmap);
Romain Guy3b748a42013-04-17 18:54:38 -0700366 }
367 }
368 }
369
John Reck9d4efdf2015-04-17 20:45:40 +0000370 private static native long nAcquireAtlasCanvas(Canvas canvas, int width, int height);
371 private static native void nReleaseAtlasCanvas(Canvas canvas, long bitmap);
372 private static native boolean nUploadAtlas(GraphicBuffer buffer, long bitmap);
Romain Guy3b748a42013-04-17 18:54:38 -0700373
374 @Override
Romain Guy80b12fc2013-05-29 15:54:25 -0700375 public boolean isCompatible(int ppid) {
376 return ppid == android.os.Process.myPpid();
377 }
378
379 @Override
Romain Guy3b748a42013-04-17 18:54:38 -0700380 public GraphicBuffer getBuffer() throws RemoteException {
381 return mAtlasReady.get() ? mBuffer : null;
382 }
383
384 @Override
Ashok Bhat17ab38f2014-01-27 16:00:23 +0000385 public long[] getMap() throws RemoteException {
Romain Guy3b748a42013-04-17 18:54:38 -0700386 return mAtlasReady.get() ? mAtlasMap : null;
387 }
388
389 /**
390 * Finds the best atlas configuration to pack the list of supplied bitmaps.
391 * This method takes advantage of multi-core systems by spawning a number
392 * of threads equal to the number of available cores.
393 */
394 private static Configuration computeBestConfiguration(
395 ArrayList<Bitmap> bitmaps, int pixelCount) {
396 if (DEBUG_ATLAS) Log.d(LOG_TAG, "Computing best atlas configuration...");
397
398 long begin = System.nanoTime();
399 List<WorkerResult> results = Collections.synchronizedList(new ArrayList<WorkerResult>());
400
401 // Don't bother with an extra thread if there's only one processor
402 int cpuCount = Runtime.getRuntime().availableProcessors();
403 if (cpuCount == 1) {
404 new ComputeWorker(MIN_SIZE, MAX_SIZE, STEP, bitmaps, pixelCount, results, null).run();
405 } else {
ztenghui072be092015-04-01 13:26:08 -0700406 int start = MIN_SIZE + (cpuCount - 1) * STEP;
407 int end = MAX_SIZE;
Romain Guy3b748a42013-04-17 18:54:38 -0700408 int step = STEP * cpuCount;
409
410 final CountDownLatch signal = new CountDownLatch(cpuCount);
411
ztenghui072be092015-04-01 13:26:08 -0700412 for (int i = 0; i < cpuCount; i++, start -= STEP, end -= STEP) {
Romain Guy3b748a42013-04-17 18:54:38 -0700413 ComputeWorker worker = new ComputeWorker(start, end, step,
414 bitmaps, pixelCount, results, signal);
415 new Thread(worker, "Atlas Worker #" + (i + 1)).start();
416 }
417
Xiaohui Chenab7f01d2015-04-07 10:12:24 -0700418 boolean isAllWorkerFinished;
Romain Guy3b748a42013-04-17 18:54:38 -0700419 try {
Xiaohui Chenab7f01d2015-04-07 10:12:24 -0700420 isAllWorkerFinished = signal.await(10, TimeUnit.SECONDS);
Romain Guy3b748a42013-04-17 18:54:38 -0700421 } catch (InterruptedException e) {
422 Log.w(LOG_TAG, "Could not complete configuration computation");
423 return null;
424 }
Xiaohui Chenab7f01d2015-04-07 10:12:24 -0700425
426 if (!isAllWorkerFinished) {
427 // We have to abort here, otherwise the async updates on "results" would crash the
428 // sort later.
429 Log.w(LOG_TAG, "Could not complete configuration computation before timeout.");
430 return null;
431 }
Romain Guy3b748a42013-04-17 18:54:38 -0700432 }
433
434 // Maximize the number of packed bitmaps, minimize the texture size
435 Collections.sort(results, new Comparator<WorkerResult>() {
436 @Override
437 public int compare(WorkerResult r1, WorkerResult r2) {
438 int delta = r2.count - r1.count;
439 if (delta != 0) return delta;
440 return r1.width * r1.height - r2.width * r2.height;
441 }
442 });
443
444 if (DEBUG_ATLAS) {
445 float delay = (System.nanoTime() - begin) / 1000.0f / 1000.0f / 1000.0f;
ztenghui072be092015-04-01 13:26:08 -0700446 Log.d(LOG_TAG, String.format("Found best atlas configuration (out of %d) in %.2fs",
447 results.size(), delay));
Romain Guy3b748a42013-04-17 18:54:38 -0700448 }
449
450 WorkerResult result = results.get(0);
451 return new Configuration(result.type, result.width, result.height, result.count);
452 }
453
454 /**
455 * Returns the path to the file containing the best computed
456 * atlas configuration.
457 */
458 private static File getDataFile() {
459 File systemDirectory = new File(Environment.getDataDirectory(), "system");
460 return new File(systemDirectory, "framework_atlas.config");
461 }
462
463 private static void deleteDataFile() {
464 Log.w(LOG_TAG, "Current configuration inconsistent with assets list");
465 if (!getDataFile().delete()) {
466 Log.w(LOG_TAG, "Could not delete the current configuration");
467 }
468 }
469
470 private File getFrameworkResourcesFile() {
471 return new File(mContext.getApplicationInfo().sourceDir);
472 }
473
474 /**
475 * Returns the best known atlas configuration. This method will either
476 * read the configuration from disk or start a brute-force search
477 * and save the result out to disk.
478 */
479 private Configuration chooseConfiguration(ArrayList<Bitmap> bitmaps, int pixelCount,
480 String versionName) {
481 Configuration config = null;
482
483 final File dataFile = getDataFile();
484 if (dataFile.exists()) {
485 config = readConfiguration(dataFile, versionName);
486 }
487
488 if (config == null) {
489 config = computeBestConfiguration(bitmaps, pixelCount);
490 if (config != null) writeConfiguration(config, dataFile, versionName);
491 }
492
493 return config;
494 }
495
496 /**
497 * Writes the specified atlas configuration to the specified file.
498 */
499 private void writeConfiguration(Configuration config, File file, String versionName) {
500 BufferedWriter writer = null;
501 try {
502 writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
503 writer.write(getBuildIdentifier(versionName));
504 writer.newLine();
505 writer.write(config.type.toString());
506 writer.newLine();
507 writer.write(String.valueOf(config.width));
508 writer.newLine();
509 writer.write(String.valueOf(config.height));
510 writer.newLine();
511 writer.write(String.valueOf(config.count));
512 writer.newLine();
513 writer.write(String.valueOf(config.flags));
514 writer.newLine();
515 } catch (FileNotFoundException e) {
516 Log.w(LOG_TAG, "Could not write " + file, e);
517 } catch (IOException e) {
518 Log.w(LOG_TAG, "Could not write " + file, e);
519 } finally {
520 if (writer != null) {
521 try {
522 writer.close();
523 } catch (IOException e) {
524 // Ignore
525 }
526 }
527 }
528 }
529
530 /**
531 * Reads an atlas configuration from the specified file. This method
532 * returns null if an error occurs or if the configuration is invalid.
533 */
534 private Configuration readConfiguration(File file, String versionName) {
535 BufferedReader reader = null;
536 Configuration config = null;
537 try {
538 reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
539
540 if (checkBuildIdentifier(reader, versionName)) {
541 Atlas.Type type = Atlas.Type.valueOf(reader.readLine());
542 int width = readInt(reader, MIN_SIZE, MAX_SIZE);
543 int height = readInt(reader, MIN_SIZE, MAX_SIZE);
544 int count = readInt(reader, 0, Integer.MAX_VALUE);
545 int flags = readInt(reader, Integer.MIN_VALUE, Integer.MAX_VALUE);
546
547 config = new Configuration(type, width, height, count, flags);
548 }
549 } catch (IllegalArgumentException e) {
550 Log.w(LOG_TAG, "Invalid parameter value in " + file, e);
551 } catch (FileNotFoundException e) {
552 Log.w(LOG_TAG, "Could not read " + file, e);
553 } catch (IOException e) {
554 Log.w(LOG_TAG, "Could not read " + file, e);
555 } finally {
556 if (reader != null) {
557 try {
558 reader.close();
559 } catch (IOException e) {
560 // Ignore
561 }
562 }
563 }
564 return config;
565 }
566
567 private static int readInt(BufferedReader reader, int min, int max) throws IOException {
568 return Math.max(min, Math.min(max, Integer.parseInt(reader.readLine())));
569 }
570
571 /**
572 * Compares the next line in the specified buffered reader to the current
573 * build identifier. Returns whether the two values are equal.
574 *
575 * @see #getBuildIdentifier(String)
576 */
577 private boolean checkBuildIdentifier(BufferedReader reader, String versionName)
578 throws IOException {
579 String deviceBuildId = getBuildIdentifier(versionName);
580 String buildId = reader.readLine();
581 return deviceBuildId.equals(buildId);
582 }
583
584 /**
585 * Returns an identifier for the current build that can be used to detect
586 * likely changes to framework resources. The build identifier is made of
587 * several distinct values:
588 *
589 * build fingerprint/framework version name/file size of framework resources apk
590 *
591 * Only the build fingerprint should be necessary on user builds but
592 * the other values are useful to detect changes on eng builds during
593 * development.
594 *
595 * This identifier does not attempt to be exact: a new identifier does not
596 * necessarily mean the preloaded drawables have changed. It is important
597 * however that whenever the list of preloaded drawables changes, this
598 * identifier changes as well.
599 *
600 * @see #checkBuildIdentifier(java.io.BufferedReader, String)
601 */
602 private String getBuildIdentifier(String versionName) {
603 return SystemProperties.get("ro.build.fingerprint", "") + '/' + versionName + '/' +
604 String.valueOf(getFrameworkResourcesFile().length());
605 }
606
607 /**
608 * Atlas configuration. Specifies the algorithm, dimensions and flags to use.
609 */
610 private static class Configuration {
611 final Atlas.Type type;
612 final int width;
613 final int height;
614 final int count;
615 final int flags;
616
617 Configuration(Atlas.Type type, int width, int height, int count) {
618 this(type, width, height, count, Atlas.FLAG_DEFAULTS);
619 }
620
621 Configuration(Atlas.Type type, int width, int height, int count, int flags) {
622 this.type = type;
623 this.width = width;
624 this.height = height;
625 this.count = count;
626 this.flags = flags;
627 }
628
629 @Override
630 public String toString() {
631 return type.toString() + " (" + width + "x" + height + ") flags=0x" +
632 Integer.toHexString(flags) + " count=" + count;
633 }
634 }
635
636 /**
637 * Used during the brute-force search to gather information about each
638 * variant of the packing algorithm.
639 */
640 private static class WorkerResult {
641 Atlas.Type type;
642 int width;
643 int height;
644 int count;
645
646 WorkerResult(Atlas.Type type, int width, int height, int count) {
647 this.type = type;
648 this.width = width;
649 this.height = height;
650 this.count = count;
651 }
652
653 @Override
654 public String toString() {
655 return String.format("%s %dx%d", type.toString(), width, height);
656 }
657 }
658
659 /**
660 * A compute worker will try a finite number of variations of the packing
661 * algorithms and save the results in a supplied list.
662 */
663 private static class ComputeWorker implements Runnable {
664 private final int mStart;
665 private final int mEnd;
666 private final int mStep;
667 private final List<Bitmap> mBitmaps;
668 private final List<WorkerResult> mResults;
669 private final CountDownLatch mSignal;
670 private final int mThreshold;
671
672 /**
673 * Creates a new compute worker to brute-force through a range of
674 * packing algorithms variants.
675 *
676 * @param start The minimum texture width to try
677 * @param end The maximum texture width to try
678 * @param step The number of pixels to increment the texture width by at each step
679 * @param bitmaps The list of bitmaps to pack in the atlas
680 * @param pixelCount The total number of pixels occupied by the list of bitmaps
681 * @param results The list of results in which to save the brute-force search results
682 * @param signal Latch to decrement when this worker is done, may be null
683 */
684 ComputeWorker(int start, int end, int step, List<Bitmap> bitmaps, int pixelCount,
685 List<WorkerResult> results, CountDownLatch signal) {
686 mStart = start;
687 mEnd = end;
688 mStep = step;
689 mBitmaps = bitmaps;
690 mResults = results;
691 mSignal = signal;
692
693 // Minimum number of pixels we want to be able to pack
694 int threshold = (int) (pixelCount * PACKING_THRESHOLD);
695 // Make sure we can find at least one configuration
696 while (threshold > MAX_SIZE * MAX_SIZE) {
697 threshold >>= 1;
698 }
699 mThreshold = threshold;
700 }
701
702 @Override
703 public void run() {
704 if (DEBUG_ATLAS) Log.d(LOG_TAG, "Running " + Thread.currentThread().getName());
705
706 Atlas.Entry entry = new Atlas.Entry();
707 for (Atlas.Type type : Atlas.Type.values()) {
ztenghui072be092015-04-01 13:26:08 -0700708 for (int width = mEnd; width > mStart; width -= mStep) {
709 for (int height = MAX_SIZE; height > MIN_SIZE; height -= STEP) {
Romain Guy3b748a42013-04-17 18:54:38 -0700710 // If the atlas is not big enough, skip it
711 if (width * height <= mThreshold) continue;
712
713 final int count = packBitmaps(type, width, height, entry);
714 if (count > 0) {
715 mResults.add(new WorkerResult(type, width, height, count));
716 // If we were able to pack everything let's stop here
717 // Increasing the height further won't make things better
718 if (count == mBitmaps.size()) {
719 break;
720 }
721 }
722 }
723 }
724 }
725
726 if (mSignal != null) {
727 mSignal.countDown();
728 }
729 }
730
731 private int packBitmaps(Atlas.Type type, int width, int height, Atlas.Entry entry) {
732 int total = 0;
733 Atlas atlas = new Atlas(type, width, height);
734
735 final int count = mBitmaps.size();
736 for (int i = 0; i < count; i++) {
737 final Bitmap bitmap = mBitmaps.get(i);
738 if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) {
739 total++;
740 }
741 }
742
743 return total;
744 }
745 }
746}