diff options
Diffstat (limited to 'src/com/android/launcher3/WidgetPreviewLoader.java')
-rw-r--r-- | src/com/android/launcher3/WidgetPreviewLoader.java | 386 |
1 files changed, 249 insertions, 137 deletions
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index 3db0b51ad..5aa719027 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -5,16 +5,17 @@ import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; -import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.database.Cursor; +import android.database.sqlite.SQLiteCantOpenDatabaseException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDiskIOException; import android.database.sqlite.SQLiteOpenHelper; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; +import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; @@ -27,129 +28,138 @@ import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.util.Log; +import com.android.launcher3.compat.AppWidgetManagerCompat; + import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; -abstract class SoftReferenceThreadLocal<T> { - private ThreadLocal<SoftReference<T>> mThreadLocal; - public SoftReferenceThreadLocal() { - mThreadLocal = new ThreadLocal<SoftReference<T>>(); - } +public class WidgetPreviewLoader { - abstract T initialValue(); + private static abstract class SoftReferenceThreadLocal<T> { + private ThreadLocal<SoftReference<T>> mThreadLocal; + public SoftReferenceThreadLocal() { + mThreadLocal = new ThreadLocal<SoftReference<T>>(); + } - public void set(T t) { - mThreadLocal.set(new SoftReference<T>(t)); - } + abstract T initialValue(); - public T get() { - SoftReference<T> reference = mThreadLocal.get(); - T obj; - if (reference == null) { - obj = initialValue(); - mThreadLocal.set(new SoftReference<T>(obj)); - return obj; - } else { - obj = reference.get(); - if (obj == null) { + public void set(T t) { + mThreadLocal.set(new SoftReference<T>(t)); + } + + public T get() { + SoftReference<T> reference = mThreadLocal.get(); + T obj; + if (reference == null) { obj = initialValue(); mThreadLocal.set(new SoftReference<T>(obj)); + return obj; + } else { + obj = reference.get(); + if (obj == null) { + obj = initialValue(); + mThreadLocal.set(new SoftReference<T>(obj)); + } + return obj; } - return obj; } } -} -class CanvasCache extends SoftReferenceThreadLocal<Canvas> { - @Override - protected Canvas initialValue() { - return new Canvas(); + private static class CanvasCache extends SoftReferenceThreadLocal<Canvas> { + @Override + protected Canvas initialValue() { + return new Canvas(); + } } -} -class PaintCache extends SoftReferenceThreadLocal<Paint> { - @Override - protected Paint initialValue() { - return null; + private static class PaintCache extends SoftReferenceThreadLocal<Paint> { + @Override + protected Paint initialValue() { + return null; + } } -} -class BitmapCache extends SoftReferenceThreadLocal<Bitmap> { - @Override - protected Bitmap initialValue() { - return null; + private static class BitmapCache extends SoftReferenceThreadLocal<Bitmap> { + @Override + protected Bitmap initialValue() { + return null; + } } -} -class RectCache extends SoftReferenceThreadLocal<Rect> { - @Override - protected Rect initialValue() { - return new Rect(); + private static class RectCache extends SoftReferenceThreadLocal<Rect> { + @Override + protected Rect initialValue() { + return new Rect(); + } } -} -class BitmapFactoryOptionsCache extends SoftReferenceThreadLocal<BitmapFactory.Options> { - @Override - protected BitmapFactory.Options initialValue() { - return new BitmapFactory.Options(); + private static class BitmapFactoryOptionsCache extends + SoftReferenceThreadLocal<BitmapFactory.Options> { + @Override + protected BitmapFactory.Options initialValue() { + return new BitmapFactory.Options(); + } } -} -public class WidgetPreviewLoader { - static final String TAG = "WidgetPreviewLoader"; - static final String ANDROID_INCREMENTAL_VERSION_NAME_KEY = "android.incremental.version"; + private static final String TAG = "WidgetPreviewLoader"; + private static final String ANDROID_INCREMENTAL_VERSION_NAME_KEY = "android.incremental.version"; + + private static final float WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE = 0.25f; + private static final HashSet<String> sInvalidPackages = new HashSet<String>(); + + // Used for drawing shortcut previews + private final BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache(); + private final PaintCache mCachedShortcutPreviewPaint = new PaintCache(); + private final CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache(); + + // Used for drawing widget previews + private final CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache(); + private final RectCache mCachedAppWidgetPreviewSrcRect = new RectCache(); + private final RectCache mCachedAppWidgetPreviewDestRect = new RectCache(); + private final PaintCache mCachedAppWidgetPreviewPaint = new PaintCache(); + private final PaintCache mDefaultAppWidgetPreviewPaint = new PaintCache(); + private final BitmapFactoryOptionsCache mCachedBitmapFactoryOptions = new BitmapFactoryOptionsCache(); + + private final HashMap<String, WeakReference<Bitmap>> mLoadedPreviews = new HashMap<>(); + private final ArrayList<SoftReference<Bitmap>> mUnusedBitmaps = new ArrayList<>(); + + private final Context mContext; + private final int mAppIconSize; + private final IconCache mIconCache; + private final AppWidgetManagerCompat mManager; private int mPreviewBitmapWidth; private int mPreviewBitmapHeight; private String mSize; - private Context mContext; - private PackageManager mPackageManager; private PagedViewCellLayout mWidgetSpacingLayout; - // Used for drawing shortcut previews - private BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache(); - private PaintCache mCachedShortcutPreviewPaint = new PaintCache(); - private CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache(); - - // Used for drawing widget previews - private CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache(); - private RectCache mCachedAppWidgetPreviewSrcRect = new RectCache(); - private RectCache mCachedAppWidgetPreviewDestRect = new RectCache(); - private PaintCache mCachedAppWidgetPreviewPaint = new PaintCache(); private String mCachedSelectQuery; - private BitmapFactoryOptionsCache mCachedBitmapFactoryOptions = new BitmapFactoryOptionsCache(); - - private int mAppIconSize; - private IconCache mIconCache; - private final float sWidgetPreviewIconPaddingPercentage = 0.25f; private CacheDb mDb; - private HashMap<String, WeakReference<Bitmap>> mLoadedPreviews; - private ArrayList<SoftReference<Bitmap>> mUnusedBitmaps; - private static HashSet<String> sInvalidPackages; - - static { - sInvalidPackages = new HashSet<String>(); - } + private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); public WidgetPreviewLoader(Context context) { LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); mContext = context; - mPackageManager = mContext.getPackageManager(); mAppIconSize = grid.iconSizePx; mIconCache = app.getIconCache(); + mManager = AppWidgetManagerCompat.getInstance(context); + mDb = app.getWidgetPreviewCacheDb(); - mLoadedPreviews = new HashMap<String, WeakReference<Bitmap>>(); - mUnusedBitmaps = new ArrayList<SoftReference<Bitmap>>(); SharedPreferences sp = context.getSharedPreferences( LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE); @@ -165,7 +175,7 @@ public class WidgetPreviewLoader { editor.commit(); } } - + public void recreateDb() { LauncherAppState app = LauncherAppState.getInstance(); app.recreateWidgetPreviewDb(); @@ -184,18 +194,19 @@ public class WidgetPreviewLoader { final String name = getObjectName(o); final String packageName = getObjectPackage(o); // check if the package is valid - boolean packageValid = true; synchronized(sInvalidPackages) { - packageValid = !sInvalidPackages.contains(packageName); - } - if (!packageValid) { - return null; + boolean packageValid = !sInvalidPackages.contains(packageName); + if (!packageValid) { + return null; + } } - if (packageValid) { - synchronized(mLoadedPreviews) { - // check if it exists in our existing cache - if (mLoadedPreviews.containsKey(name) && mLoadedPreviews.get(name).get() != null) { - return mLoadedPreviews.get(name).get(); + synchronized(mLoadedPreviews) { + // check if it exists in our existing cache + if (mLoadedPreviews.containsKey(name)) { + WeakReference<Bitmap> bitmapReference = mLoadedPreviews.get(name); + Bitmap bitmap = bitmapReference.get(); + if (bitmap != null) { + return bitmap; } } } @@ -203,11 +214,13 @@ public class WidgetPreviewLoader { Bitmap unusedBitmap = null; synchronized(mUnusedBitmaps) { // not in cache; we need to load it from the db - while ((unusedBitmap == null || !unusedBitmap.isMutable() || - unusedBitmap.getWidth() != mPreviewBitmapWidth || - unusedBitmap.getHeight() != mPreviewBitmapHeight) - && mUnusedBitmaps.size() > 0) { - unusedBitmap = mUnusedBitmaps.remove(0).get(); + while (unusedBitmap == null && mUnusedBitmaps.size() > 0) { + Bitmap candidate = mUnusedBitmaps.remove(0).get(); + if (candidate != null && candidate.isMutable() && + candidate.getWidth() == mPreviewBitmapWidth && + candidate.getHeight() == mPreviewBitmapHeight) { + unusedBitmap = candidate; + } } if (unusedBitmap != null) { final Canvas c = mCachedAppWidgetPreviewCanvas.get(); @@ -221,12 +234,7 @@ public class WidgetPreviewLoader { unusedBitmap = Bitmap.createBitmap(mPreviewBitmapWidth, mPreviewBitmapHeight, Bitmap.Config.ARGB_8888); } - - Bitmap preview = null; - - if (packageValid) { - preview = readFromDb(name, unusedBitmap); - } + Bitmap preview = readFromDb(name, unusedBitmap); if (preview != null) { synchronized(mLoadedPreviews) { @@ -320,7 +328,7 @@ public class WidgetPreviewLoader { String output; if (o instanceof AppWidgetProviderInfo) { sb.append(WIDGET_PREFIX); - sb.append(((AppWidgetProviderInfo) o).provider.flattenToString()); + sb.append(((AppWidgetProviderInfo) o).toString()); output = sb.toString(); sb.setLength(0); } else { @@ -358,6 +366,9 @@ public class WidgetPreviewLoader { db.insert(CacheDb.TABLE_NAME, null, values); } catch (SQLiteDiskIOException e) { recreateDb(); + } catch (SQLiteCantOpenDatabaseException e) { + dumpOpenFiles(); + throw e; } } @@ -367,6 +378,9 @@ public class WidgetPreviewLoader { try { db.delete(CacheDb.TABLE_NAME, null, null); } catch (SQLiteDiskIOException e) { + } catch (SQLiteCantOpenDatabaseException e) { + dumpOpenFiles(); + throw e; } } @@ -387,6 +401,9 @@ public class WidgetPreviewLoader { } // args to SELECT query ); } catch (SQLiteDiskIOException e) { + } catch (SQLiteCantOpenDatabaseException e) { + dumpOpenFiles(); + throw e; } synchronized(sInvalidPackages) { sInvalidPackages.remove(packageName); @@ -396,7 +413,7 @@ public class WidgetPreviewLoader { }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); } - public static void removeItemFromDb(final CacheDb cacheDb, final String objectName) { + private static void removeItemFromDb(final CacheDb cacheDb, final String objectName) { new AsyncTask<Void, Void, Void>() { public Void doInBackground(Void ... args) { SQLiteDatabase db = cacheDb.getWritableDatabase(); @@ -405,6 +422,9 @@ public class WidgetPreviewLoader { CacheDb.COLUMN_NAME + " = ? ", // SELECT query new String[] { objectName }); // args to SELECT query } catch (SQLiteDiskIOException e) { + } catch (SQLiteCantOpenDatabaseException e) { + dumpOpenFiles(); + throw e; } return null; } @@ -430,6 +450,9 @@ public class WidgetPreviewLoader { } catch (SQLiteDiskIOException e) { recreateDb(); return null; + } catch (SQLiteCantOpenDatabaseException e) { + dumpOpenFiles(); + throw e; } if (result.getCount() > 0) { result.moveToFirst(); @@ -450,7 +473,7 @@ public class WidgetPreviewLoader { } } - public Bitmap generatePreview(Object info, Bitmap preview) { + private Bitmap generatePreview(Object info, Bitmap preview) { if (preview != null && (preview.getWidth() != mPreviewBitmapWidth || preview.getHeight() != mPreviewBitmapHeight)) { @@ -468,8 +491,8 @@ public class WidgetPreviewLoader { int[] cellSpans = Launcher.getSpanForWidget(mContext, info); int maxWidth = maxWidthForWidgetPreview(cellSpans[0]); int maxHeight = maxHeightForWidgetPreview(cellSpans[1]); - return generateWidgetPreview(info.provider, info.previewImage, info.icon, - cellSpans[0], cellSpans[1], maxWidth, maxHeight, preview, null); + return generateWidgetPreview(info, cellSpans[0], cellSpans[1], + maxWidth, maxHeight, preview, null); } public int maxWidthForWidgetPreview(int spanX) { @@ -482,20 +505,20 @@ public class WidgetPreviewLoader { mWidgetSpacingLayout.estimateCellHeight(spanY)); } - public Bitmap generateWidgetPreview(ComponentName provider, int previewImage, - int iconId, int cellHSpan, int cellVSpan, int maxPreviewWidth, int maxPreviewHeight, - Bitmap preview, int[] preScaledWidthOut) { + public Bitmap generateWidgetPreview(AppWidgetProviderInfo info, int cellHSpan, int cellVSpan, + int maxPreviewWidth, int maxPreviewHeight, Bitmap preview, int[] preScaledWidthOut) { // Load the preview image if possible - String packageName = provider.getPackageName(); if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE; if (maxPreviewHeight < 0) maxPreviewHeight = Integer.MAX_VALUE; Drawable drawable = null; - if (previewImage != 0) { - drawable = mPackageManager.getDrawable(packageName, previewImage, null); - if (drawable == null) { + if (info.previewImage != 0) { + drawable = mManager.loadPreview(info); + if (drawable != null) { + drawable = mutateOnMainThread(drawable); + } else { Log.w(TAG, "Can't load widget preview drawable 0x" + - Integer.toHexString(previewImage) + " for provider: " + provider); + Integer.toHexString(info.previewImage) + " for provider: " + info.provider); } } @@ -511,6 +534,7 @@ public class WidgetPreviewLoader { if (cellHSpan < 1) cellHSpan = 1; if (cellVSpan < 1) cellVSpan = 1; + // This Drawable is not directly drawn, so there's no need to mutate it. BitmapDrawable previewDrawable = (BitmapDrawable) mContext.getResources() .getDrawable(R.drawable.widget_tile); final int previewDrawableWidth = previewDrawable @@ -520,31 +544,33 @@ public class WidgetPreviewLoader { previewWidth = previewDrawableWidth * cellHSpan; previewHeight = previewDrawableHeight * cellVSpan; - defaultPreview = Bitmap.createBitmap(previewWidth, previewHeight, - Config.ARGB_8888); + defaultPreview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888); final Canvas c = mCachedAppWidgetPreviewCanvas.get(); c.setBitmap(defaultPreview); - previewDrawable.setBounds(0, 0, previewWidth, previewHeight); - previewDrawable.setTileModeXY(Shader.TileMode.REPEAT, - Shader.TileMode.REPEAT); - previewDrawable.draw(c); + Paint p = mDefaultAppWidgetPreviewPaint.get(); + if (p == null) { + p = new Paint(); + p.setShader(new BitmapShader(previewDrawable.getBitmap(), + Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)); + mDefaultAppWidgetPreviewPaint.set(p); + } + final Rect dest = mCachedAppWidgetPreviewDestRect.get(); + dest.set(0, 0, previewWidth, previewHeight); + c.drawRect(dest, p); c.setBitmap(null); // Draw the icon in the top left corner - int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage); + int minOffset = (int) (mAppIconSize * WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE); int smallestSide = Math.min(previewWidth, previewHeight); float iconScale = Math.min((float) smallestSide / (mAppIconSize + 2 * minOffset), 1f); try { - Drawable icon = null; - int hoffset = - (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2); - int yoffset = - (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2); - if (iconId > 0) - icon = mIconCache.getFullResIcon(packageName, iconId); + Drawable icon = mManager.loadIcon(info, mIconCache); if (icon != null) { + int hoffset = (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2); + int yoffset = (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2); + icon = mutateOnMainThread(icon); renderDrawableToBitmap(icon, defaultPreview, hoffset, yoffset, (int) (mAppIconSize * iconScale), (int) (mAppIconSize * iconScale)); @@ -594,7 +620,7 @@ public class WidgetPreviewLoader { c.drawBitmap(defaultPreview, src, dest, p); c.setBitmap(null); } - return preview; + return mManager.getBadgeBitmap(info, preview); } private Bitmap generateShortcutPreview( @@ -612,7 +638,7 @@ public class WidgetPreviewLoader { c.setBitmap(null); } // Render the icon - Drawable icon = mIconCache.getFullResIcon(info); + Drawable icon = mutateOnMainThread(mIconCache.getFullResIcon(info)); int paddingTop = mContext. getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top); @@ -652,18 +678,10 @@ public class WidgetPreviewLoader { return preview; } - - public static void renderDrawableToBitmap( - Drawable d, Bitmap bitmap, int x, int y, int w, int h) { - renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f); - } - private static void renderDrawableToBitmap( - Drawable d, Bitmap bitmap, int x, int y, int w, int h, - float scale) { + Drawable d, Bitmap bitmap, int x, int y, int w, int h) { if (bitmap != null) { Canvas c = new Canvas(bitmap); - c.scale(scale, scale); Rect oldBounds = d.copyBounds(); d.setBounds(x, y, x + w, y + h); d.draw(c); @@ -672,4 +690,98 @@ public class WidgetPreviewLoader { } } + private Drawable mutateOnMainThread(final Drawable drawable) { + try { + return mMainThreadExecutor.submit(new Callable<Drawable>() { + @Override + public Drawable call() throws Exception { + return drawable.mutate(); + } + }).get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + private static final int MAX_OPEN_FILES = 1024; + private static final int SAMPLE_RATE = 23; + /** + * Dumps all files that are open in this process without allocating a file descriptor. + */ + private static void dumpOpenFiles() { + try { + Log.i(TAG, "DUMP OF OPEN FILES (sample rate: 1 every " + SAMPLE_RATE + "):"); + final String TYPE_APK = "apk"; + final String TYPE_JAR = "jar"; + final String TYPE_PIPE = "pipe"; + final String TYPE_SOCKET = "socket"; + final String TYPE_DB = "db"; + final String TYPE_ANON_INODE = "anon_inode"; + final String TYPE_DEV = "dev"; + final String TYPE_NON_FS = "non-fs"; + final String TYPE_OTHER = "other"; + List<String> types = Arrays.asList(TYPE_APK, TYPE_JAR, TYPE_PIPE, TYPE_SOCKET, TYPE_DB, + TYPE_ANON_INODE, TYPE_DEV, TYPE_NON_FS, TYPE_OTHER); + int[] count = new int[types.size()]; + int[] duplicates = new int[types.size()]; + HashSet<String> files = new HashSet<String>(); + int total = 0; + for (int i = 0; i < MAX_OPEN_FILES; i++) { + // This is a gigantic hack but unfortunately the only way to resolve an fd + // to a file name. Note that we have to loop over all possible fds because + // reading the directory would require allocating a new fd. The kernel is + // currently implemented such that no fd is larger then the current rlimit, + // which is why it's safe to loop over them in such a way. + String fd = "/proc/self/fd/" + i; + try { + // getCanonicalPath() uses readlink behind the scene which doesn't require + // a file descriptor. + String resolved = new File(fd).getCanonicalPath(); + int type = types.indexOf(TYPE_OTHER); + if (resolved.startsWith("/dev/")) { + type = types.indexOf(TYPE_DEV); + } else if (resolved.endsWith(".apk")) { + type = types.indexOf(TYPE_APK); + } else if (resolved.endsWith(".jar")) { + type = types.indexOf(TYPE_JAR); + } else if (resolved.contains("/fd/pipe:")) { + type = types.indexOf(TYPE_PIPE); + } else if (resolved.contains("/fd/socket:")) { + type = types.indexOf(TYPE_SOCKET); + } else if (resolved.contains("/fd/anon_inode:")) { + type = types.indexOf(TYPE_ANON_INODE); + } else if (resolved.endsWith(".db") || resolved.contains("/databases/")) { + type = types.indexOf(TYPE_DB); + } else if (resolved.startsWith("/proc/") && resolved.contains("/fd/")) { + // Those are the files that don't point anywhere on the file system. + // getCanonicalPath() wrongly interprets these as relative symlinks and + // resolves them within /proc/<pid>/fd/. + type = types.indexOf(TYPE_NON_FS); + } + count[type]++; + total++; + if (files.contains(resolved)) { + duplicates[type]++; + } + files.add(resolved); + if (total % SAMPLE_RATE == 0) { + Log.i(TAG, " fd " + i + ": " + resolved + + " (" + types.get(type) + ")"); + } + } catch (IOException e) { + // Ignoring exceptions for non-existing file descriptors. + } + } + for (int i = 0; i < types.size(); i++) { + Log.i(TAG, String.format("Open %10s files: %4d total, %4d duplicates", + types.get(i), count[i], duplicates[i])); + } + } catch (Throwable t) { + // Catch everything. This is called from an exception handler that we shouldn't upset. + Log.e(TAG, "Unable to log open files.", t); + } + } } |