From 5b0e669169ea2c951bf2f6f71faf793b24db3c23 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 19 Mar 2015 14:31:19 -0700 Subject: Rewrite: Widget preview loader logic > Widget previews are saved in data dir instead of cache dir > Expiring widget previews similar to IconCache > Removed support for setting thread priorities Bug: 19865031 Change-Id: Ib6033c2b1ff8ae61bba8762ca994ccd8217d3c75 --- .../android/launcher3/AppsCustomizePagedView.java | 388 ++--------- src/com/android/launcher3/IconCache.java | 4 +- .../android/launcher3/InstallShortcutReceiver.java | 2 +- src/com/android/launcher3/ItemInfo.java | 21 +- src/com/android/launcher3/LauncherAppState.java | 21 +- .../android/launcher3/LauncherBackupHelper.java | 14 +- src/com/android/launcher3/LauncherModel.java | 9 +- .../android/launcher3/PackageChangedReceiver.java | 10 +- src/com/android/launcher3/PagedViewCellLayout.java | 476 ------------- .../launcher3/PagedViewCellLayoutChildren.java | 161 ----- src/com/android/launcher3/PagedViewWidget.java | 83 ++- src/com/android/launcher3/Utilities.java | 21 + src/com/android/launcher3/WidgetPreviewLoader.java | 758 ++++++++++----------- src/com/android/launcher3/Workspace.java | 10 +- 14 files changed, 524 insertions(+), 1454 deletions(-) delete mode 100644 src/com/android/launcher3/PagedViewCellLayout.java delete mode 100644 src/com/android/launcher3/PagedViewCellLayoutChildren.java (limited to 'src') diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java index 1955547c8..58bcf1dbe 100644 --- a/src/com/android/launcher3/AppsCustomizePagedView.java +++ b/src/com/android/launcher3/AppsCustomizePagedView.java @@ -29,10 +29,8 @@ import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.os.Process; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; @@ -48,90 +46,6 @@ import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.util.Thunk; import java.util.ArrayList; -import java.util.Iterator; - -/** - * A simple callback interface which also provides the results of the task. - */ -interface AsyncTaskCallback { - void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data); -} - -/** - * The data needed to perform either of the custom AsyncTasks. - */ -class AsyncTaskPageData { - enum Type { - LoadWidgetPreviewData - } - - AsyncTaskPageData(int p, ArrayList l, int cw, int ch, AsyncTaskCallback bgR, - AsyncTaskCallback postR, WidgetPreviewLoader w) { - page = p; - items = l; - generatedImages = new ArrayList(); - maxImageWidth = cw; - maxImageHeight = ch; - doInBackgroundCallback = bgR; - postExecuteCallback = postR; - widgetPreviewLoader = w; - } - void cleanup(boolean cancelled) { - // Clean up any references to source/generated bitmaps - if (generatedImages != null) { - if (cancelled) { - for (int i = 0; i < generatedImages.size(); i++) { - widgetPreviewLoader.recycleBitmap(items.get(i), generatedImages.get(i)); - } - } - generatedImages.clear(); - } - } - int page; - ArrayList items; - ArrayList sourceImages; - ArrayList generatedImages; - int maxImageWidth; - int maxImageHeight; - AsyncTaskCallback doInBackgroundCallback; - AsyncTaskCallback postExecuteCallback; - WidgetPreviewLoader widgetPreviewLoader; -} - -/** - * A generic template for an async task used in AppsCustomize. - */ -class AppsCustomizeAsyncTask extends AsyncTask { - AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) { - page = p; - threadPriority = Process.THREAD_PRIORITY_DEFAULT; - dataType = ty; - } - @Override - protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) { - if (params.length != 1) return null; - // Load each of the widget previews in the background - params[0].doInBackgroundCallback.run(this, params[0]); - return params[0]; - } - @Override - protected void onPostExecute(AsyncTaskPageData result) { - // All the widget previews are loaded, so we can just callback to inflate the page - result.postExecuteCallback.run(this, result); - } - - void setThreadPriority(int p) { - threadPriority = p; - } - void syncThreadPriority() { - Process.setThreadPriority(threadPriority); - } - - // The page that this async task is associated with - AsyncTaskPageData.Type dataType; - int page; - int threadPriority; -} /** * The Apps/Customize page that displays all the applications, widgets, and shortcuts. @@ -142,6 +56,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen static final String TAG = "AppsCustomizePagedView"; private static Rect sTmpRect = new Rect(); + private static final int[] sTempPosArray = new int[2]; /** * The different content types that this paged view can show. @@ -171,10 +86,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen @Thunk int mWidgetCountX, mWidgetCountY; private int mNumWidgetPages; - // Previews & outlines - ArrayList mRunningTasks; - private static final int sPageSleepDelay = 200; - private final PagedViewKeyListener mKeyListener = new PagedViewKeyListener(); private Runnable mInflateWidgetRunnable = null; @@ -192,11 +103,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen private Toast mWidgetInstructionToast; // Deferral of loading widget previews during launcher transitions - @Thunk boolean mInTransition; - private ArrayList mDeferredSyncWidgetPageItems = - new ArrayList(); - @Thunk ArrayList mDeferredPrepareLoadWidgetPreviewsTasks = - new ArrayList(); + private boolean mInTransition; WidgetPreviewLoader mWidgetPreviewLoader; @@ -209,7 +116,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen mPackageManager = context.getPackageManager(); mWidgets = new ArrayList<>(); mIconCache = (LauncherAppState.getInstance()).getIconCache(); - mRunningTasks = new ArrayList<>(); // Save the default widget preview background TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0); @@ -253,7 +159,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen WidgetPreviewLoader getWidgetPreviewLoader() { if (mWidgetPreviewLoader == null) { - mWidgetPreviewLoader = new WidgetPreviewLoader(mLauncher); + mWidgetPreviewLoader = LauncherAppState.getInstance().getWidgetCache(); } return mWidgetPreviewLoader; } @@ -484,8 +390,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen info.boundWidget = hostView; mWidgetCleanupState = WIDGET_INFLATED; hostView.setVisibility(INVISIBLE); - int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info.spanX, - info.spanY, info, false); + int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info, false); // We want the first widget layout to be the correct size. This will be important // for width size reporting to the AppWidgetManager. @@ -554,7 +459,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen } } - private boolean beginDraggingWidget(View v) { + private boolean beginDraggingWidget(PagedViewWidget v) { mDraggingWidget = true; // Get the widget preview as the drag representation ImageView image = (ImageView) v.findViewById(R.id.widget_preview); @@ -582,10 +487,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen PendingAddWidgetInfo createWidgetInfo = mCreateWidgetInfo; createItemInfo = createWidgetInfo; - int spanX = createItemInfo.spanX; - int spanY = createItemInfo.spanY; - int[] size = mLauncher.getWorkspace().estimateItemSize(spanX, spanY, - createWidgetInfo, true); + int[] size = mLauncher.getWorkspace().estimateItemSize(createWidgetInfo, true); FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable(); float minScale = 1.25f; @@ -594,10 +496,9 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen int[] previewSizeBeforeScale = new int[1]; preview = getWidgetPreviewLoader().generateWidgetPreview(createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale); - // Compare the size of the drag preview to the preview in the AppsCustomize tray int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0], - getWidgetPreviewLoader().maxWidthForWidgetPreview(spanX)); + v.getActualItemWidth()); scale = previewWidthInAppsCustomize / (float) preview.getWidth(); // The bitmap in the AppsCustomize tray is always the the same size, so there @@ -637,7 +538,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen if (!super.beginDragging(v)) return false; if (v instanceof PagedViewWidget) { - if (!beginDraggingWidget(v)) { + if (!beginDraggingWidget((PagedViewWidget) v)) { return false; } } else { @@ -690,7 +591,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { mInTransition = true; if (toWorkspace) { - cancelAllTasks(); + cancelAllTasks(false); } } @@ -705,15 +606,10 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen @Override public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { mInTransition = false; - for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) { - onSyncWidgetPageItems(d, false); - } - mDeferredSyncWidgetPageItems.clear(); - for (Runnable r : mDeferredPrepareLoadWidgetPreviewsTasks) { - r.run(); - } - mDeferredPrepareLoadWidgetPreviewsTasks.clear(); mForceDrawAllChildrenNextFrame = !toWorkspace; + if (!toWorkspace) { + loadPreviewsForPage(getNextPage()); + } } @Override @@ -782,45 +678,26 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - cancelAllTasks(); + cancelAllTasks(true); } @Override public void trimMemory() { super.trimMemory(); - clearAllWidgetPages(); + cancelAllTasks(true); } - public void clearAllWidgetPages() { - cancelAllTasks(); - int count = getChildCount(); - for (int i = 0; i < count; i++) { - View v = getPageAt(i); - if (v instanceof PagedViewGridLayout) { - ((PagedViewGridLayout) v).removeAllViewsOnPage(); - mDirtyPageContent.set(i, true); + private void cancelAllTasks(boolean clearCompletedTasks) { + for (int page = getPageCount() - 1; page >= 0; page--) { + final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page); + if (layout != null) { + for (int i = 0; i < layout.getChildCount(); i++) { + ((PagedViewWidget) layout.getChildAt(i)).deletePreview(clearCompletedTasks); + } } } } - private void cancelAllTasks() { - // Clean up all the async tasks - Iterator iter = mRunningTasks.iterator(); - while (iter.hasNext()) { - AppsCustomizeAsyncTask task = iter.next(); - task.cancel(false); - iter.remove(); - mDirtyPageContent.set(task.page, true); - - // We've already preallocated the views for the data to load into, so clear them as well - View v = getPageAt(task.page); - if (v instanceof PagedViewGridLayout) { - ((PagedViewGridLayout) v).removeAllViewsOnPage(); - } - } - mDeferredSyncWidgetPageItems.clear(); - mDeferredPrepareLoadWidgetPreviewsTasks.clear(); - } public void setContentType(ContentType type) { // Widgets appear to be cleared every time you leave, always force invalidate for them @@ -835,23 +712,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen return mContentType; } - protected void snapToPage(int whichPage, int delta, int duration) { - super.snapToPage(whichPage, delta, duration); - - // Update the thread priorities given the direction lookahead - Iterator iter = mRunningTasks.iterator(); - while (iter.hasNext()) { - AppsCustomizeAsyncTask task = iter.next(); - int pageIndex = task.page; - if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) || - (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) { - task.setThreadPriority(getThreadPriorityForPage(pageIndex)); - } else { - task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST); - } - } - } - public void setPageBackgroundsVisible(boolean visible) { mPageBackgroundsVisible = visible; int childCount = getChildCount(); @@ -863,104 +723,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen } } - /** - * A helper to return the priority for loading of the specified widget page. - */ - private int getWidgetPageLoadPriority(int page) { - // If we are snapping to another page, use that index as the target page index - int toPage = mCurrentPage; - if (mNextPage > -1) { - toPage = mNextPage; - } - - // We use the distance from the target page as an initial guess of priority, but if there - // are no pages of higher priority than the page specified, then bump up the priority of - // the specified page. - Iterator iter = mRunningTasks.iterator(); - int minPageDiff = Integer.MAX_VALUE; - while (iter.hasNext()) { - AppsCustomizeAsyncTask task = iter.next(); - minPageDiff = Math.abs(task.page - toPage); - } - - int rawPageDiff = Math.abs(page - toPage); - return rawPageDiff - Math.min(rawPageDiff, minPageDiff); - } - /** - * Return the appropriate thread priority for loading for a given page (we give the current - * page much higher priority) - */ - private int getThreadPriorityForPage(int page) { - // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below - int pageDiff = getWidgetPageLoadPriority(page); - if (pageDiff <= 0) { - return Process.THREAD_PRIORITY_LESS_FAVORABLE; - } else if (pageDiff <= 1) { - return Process.THREAD_PRIORITY_LOWEST; - } else { - return Process.THREAD_PRIORITY_LOWEST; - } - } - private int getSleepForPage(int page) { - int pageDiff = getWidgetPageLoadPriority(page); - return Math.max(0, pageDiff * sPageSleepDelay); - } - /** - * Creates and executes a new AsyncTask to load a page of widget previews. - */ - @Thunk void prepareLoadWidgetPreviewsTask(int page, ArrayList widgets, - int cellWidth, int cellHeight, int cellCountX) { - - // Prune all tasks that are no longer needed - Iterator iter = mRunningTasks.iterator(); - while (iter.hasNext()) { - AppsCustomizeAsyncTask task = iter.next(); - int taskPage = task.page; - if (taskPage < getAssociatedLowerPageBound(mCurrentPage) || - taskPage > getAssociatedUpperPageBound(mCurrentPage)) { - task.cancel(false); - iter.remove(); - } else { - task.setThreadPriority(getThreadPriorityForPage(taskPage)); - } - } - - // We introduce a slight delay to order the loading of side pages so that we don't thrash - final int sleepMs = getSleepForPage(page); - AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight, - new AsyncTaskCallback() { - @Override - public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { - try { - try { - Thread.sleep(sleepMs); - } catch (Exception e) {} - loadWidgetPreviewsInBackground(task, data); - } finally { - if (task.isCancelled()) { - data.cleanup(true); - } - } - } - }, - new AsyncTaskCallback() { - @Override - public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { - mRunningTasks.remove(task); - if (task.isCancelled()) return; - // do cleanup inside onSyncWidgetPageItems - onSyncWidgetPageItems(data, false); - } - }, getWidgetPreviewLoader()); - - // Ensure that the task is appropriately prioritized and runs in parallel - AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page, - AsyncTaskPageData.Type.LoadWidgetPreviewData); - t.setThreadPriority(getThreadPriorityForPage(page)); - t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData); - mRunningTasks.add(t); - } - /* * Widgets PagedView implementation */ @@ -1051,95 +813,18 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen layout.addView(widget, lp); } - // wait until a call on onLayout to start loading, because - // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out - // TODO: can we do a measure/layout immediately? - layout.setOnLayoutListener(new Runnable() { - public void run() { - // Load the widget previews - int maxPreviewWidth = cellWidth; - int maxPreviewHeight = cellHeight; - if (layout.getChildCount() > 0) { - PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0); - int[] maxSize = w.getPreviewSize(); - maxPreviewWidth = maxSize[0]; - maxPreviewHeight = maxSize[1]; - } - - getWidgetPreviewLoader().setPreviewSize(maxPreviewWidth, maxPreviewHeight); - if (immediate) { - AsyncTaskPageData data = new AsyncTaskPageData(page, items, - maxPreviewWidth, maxPreviewHeight, null, null, getWidgetPreviewLoader()); - loadWidgetPreviewsInBackground(null, data); - onSyncWidgetPageItems(data, immediate); - } else { - if (mInTransition) { - mDeferredPrepareLoadWidgetPreviewsTasks.add(this); - } else { - prepareLoadWidgetPreviewsTask(page, items, - maxPreviewWidth, maxPreviewHeight, mWidgetCountX); - } - } - layout.setOnLayoutListener(null); - } - }); - } - @Thunk void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task, - AsyncTaskPageData data) { - // loadWidgetPreviewsInBackground can be called without a task to load a set of widget - // previews synchronously - if (task != null) { - // Ensure that this task starts running at the correct priority - task.syncThreadPriority(); - } - - // Load each of the widget/shortcut previews - ArrayList items = data.items; - ArrayList images = data.generatedImages; - int count = items.size(); - for (int i = 0; i < count; ++i) { - if (task != null) { - // Ensure we haven't been cancelled yet - if (task.isCancelled()) break; - // Before work on each item, ensure that this task is running at the correct - // priority - task.syncThreadPriority(); - } - - images.add(getWidgetPreviewLoader().getPreview(items.get(i))); + if (immediate && !mInTransition) { + loadPreviewsForPage(page); } } - @Thunk void onSyncWidgetPageItems(AsyncTaskPageData data, boolean immediatelySyncItems) { - if (!immediatelySyncItems && mInTransition) { - mDeferredSyncWidgetPageItems.add(data); - return; - } - try { - int page = data.page; - PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page); - - ArrayList items = data.items; - int count = items.size(); - for (int i = 0; i < count; ++i) { - PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i); - if (widget != null) { - Bitmap preview = data.generatedImages.get(i); - widget.applyPreview(new FastBitmapDrawable(preview), i); - } - } - - enableHwLayersOnVisiblePages(); + private void loadPreviewsForPage(int page) { + final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page); - // Update all thread priorities - Iterator iter = mRunningTasks.iterator(); - while (iter.hasNext()) { - AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); - int pageIndex = task.page; - task.setThreadPriority(getThreadPriorityForPage(pageIndex)); + if (layout != null) { + for (int i = 0; i < layout.getChildCount(); i++) { + ((PagedViewWidget) layout.getChildAt(i)).ensurePreview(); } - } finally { - data.cleanup(false); } } @@ -1148,7 +833,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen disablePagedViewAnimations(); removeAllViews(); - cancelAllTasks(); + cancelAllTasks(true); Context context = getContext(); if (mContentType == ContentType.Widgets) { @@ -1254,6 +939,17 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen mSaveInstanceStateItemIndex = -1; } + @Override + protected void onPageBeginMoving() { + super.onPageBeginMoving(); + if (!mInTransition) { + getVisiblePages(sTempPosArray); + for (int i = sTempPosArray[0]; i <= sTempPosArray[1]; i++) { + loadPreviewsForPage(i); + } + } + } + /* * AllAppsView implementation */ @@ -1274,7 +970,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen // request a layout to trigger the page data when ready. requestLayout(); } else { - cancelAllTasks(); + cancelAllTasks(false); invalidatePageData(); } } @@ -1324,7 +1020,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen // should stop this now. // Stop all background tasks - cancelAllTasks(); + cancelAllTasks(true); } /* diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index 3c7adbe8d..9b2119e0b 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -656,8 +656,8 @@ public class IconCache { public ContentValues newContentValues(Bitmap icon, String label) { ContentValues values = new ContentValues(); - values.put(IconDB.COLUMN_ICON, ItemInfo.flattenBitmap(icon)); - values.put(IconDB.COLUMN_ICON_LOW_RES, ItemInfo.flattenBitmap( + values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon)); + values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap( Bitmap.createScaledBitmap(icon, icon.getWidth() / LOW_RES_SCALE_FACTOR, icon.getHeight() / LOW_RES_SCALE_FACTOR, true))); diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index 4349e16c1..0db22a499 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -331,7 +331,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0)) .key(NAME_KEY).value(name); if (icon != null) { - byte[] iconByteArray = ItemInfo.flattenBitmap(icon); + byte[] iconByteArray = Utilities.flattenBitmap(icon); json = json.key(ICON_KEY).value( Base64.encodeToString( iconByteArray, 0, iconByteArray.length, Base64.DEFAULT)); diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java index aff8323c2..f114de221 100644 --- a/src/com/android/launcher3/ItemInfo.java +++ b/src/com/android/launcher3/ItemInfo.java @@ -20,13 +20,10 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.util.Log; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.util.Arrays; /** @@ -177,25 +174,9 @@ public class ItemInfo { } } - static byte[] flattenBitmap(Bitmap bitmap) { - // Try go guesstimate how much space the icon will take when serialized - // to avoid unnecessary allocations/copies during the write. - int size = bitmap.getWidth() * bitmap.getHeight() * 4; - ByteArrayOutputStream out = new ByteArrayOutputStream(size); - try { - bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); - out.flush(); - out.close(); - return out.toByteArray(); - } catch (IOException e) { - Log.w("Favorite", "Could not write icon"); - return null; - } - } - static void writeBitmap(ContentValues values, Bitmap bitmap) { if (bitmap != null) { - byte[] data = flattenBitmap(bitmap); + byte[] data = Utilities.flattenBitmap(bitmap); values.put(LauncherSettings.Favorites.ICON, data); } } diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 9082276e4..555b1ccad 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -32,7 +32,6 @@ import android.os.Handler; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; -import android.view.View.AccessibilityDelegate; import android.view.WindowManager; import com.android.launcher3.compat.LauncherAppsCompat; @@ -49,12 +48,12 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks { private final BuildInfo mBuildInfo; @Thunk final LauncherModel mModel; private final IconCache mIconCache; + private final WidgetPreviewLoader mWidgetCache; private final boolean mIsScreenLarge; private final float mScreenDensity; private final int mLongPressTimeout = 300; - private WidgetPreviewLoader.CacheDb mWidgetPreviewCacheDb; private boolean mWallpaperChangedSinceLastCheck; private static WeakReference sLauncherProvider; @@ -101,9 +100,8 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks { // set sIsScreenXLarge and mScreenDensity *before* creating icon cache mIsScreenLarge = isScreenLarge(sContext.getResources()); mScreenDensity = sContext.getResources().getDisplayMetrics().density; - - recreateWidgetPreviewDb(); mIconCache = new IconCache(sContext); + mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache); mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class)); mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class)); @@ -125,13 +123,6 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks { mFavoritesObserver); } - public void recreateWidgetPreviewDb() { - if (mWidgetPreviewCacheDb != null) { - mWidgetPreviewCacheDb.close(); - } - mWidgetPreviewCacheDb = new WidgetPreviewLoader.CacheDb(sContext); - } - /** * Call from Application.onTerminate(), which is not guaranteed to ever be called. */ @@ -181,10 +172,6 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks { return mAppFilter == null || mAppFilter.shouldShowApp(componentName); } - WidgetPreviewLoader.CacheDb getWidgetPreviewCacheDb() { - return mWidgetPreviewCacheDb; - } - static void setLauncherProvider(LauncherProvider provider) { sLauncherProvider = new WeakReference(provider); } @@ -240,6 +227,10 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks { return mDynamicGrid; } + public WidgetPreviewLoader getWidgetCache() { + return mWidgetCache; + } + public boolean isScreenLarge() { return mIsScreenLarge; } diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java index 57f92bc20..c03480065 100644 --- a/src/com/android/launcher3/LauncherBackupHelper.java +++ b/src/com/android/launcher3/LauncherBackupHelper.java @@ -630,7 +630,7 @@ public class LauncherBackupHelper implements BackupHelper { return; } final ContentResolver cr = mContext.getContentResolver(); - final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext); + final WidgetPreviewLoader previewLoader = appState.getWidgetCache(); final int dpi = mContext.getResources().getDisplayMetrics().densityDpi; final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile(); if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx); @@ -646,7 +646,6 @@ public class LauncherBackupHelper implements BackupHelper { final long id = cursor.getLong(ID_INDEX); final String providerName = cursor.getString(APPWIDGET_PROVIDER_INDEX); final int spanX = cursor.getInt(SPANX_INDEX); - final int spanY = cursor.getInt(SPANY_INDEX); final ComponentName provider = ComponentName.unflattenFromString(providerName); Key key = null; String backupKey = null; @@ -665,12 +664,10 @@ public class LauncherBackupHelper implements BackupHelper { if (DEBUG) Log.d(TAG, "I can count this high: " + backupWidgetCount); if (backupWidgetCount < MAX_WIDGETS_PER_PASS) { if (DEBUG) Log.d(TAG, "saving widget " + backupKey); - previewLoader.setPreviewSize( - spanX * profile.cellWidthPx, - spanY * profile.cellHeightPx); UserHandleCompat user = UserHandleCompat.myUserHandle(); writeRowToBackup(key, - packWidget(dpi, previewLoader, mIconCache, provider, user), + packWidget(dpi, previewLoader,spanX * profile.cellWidthPx, + mIconCache, provider, user), data); mKeys.add(key); backupWidgetCount ++; @@ -980,7 +977,8 @@ public class LauncherBackupHelper implements BackupHelper { } /** Serialize a widget for persistence, including a checksum wrapper. */ - private Widget packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache, + private Widget packWidget(int dpi, WidgetPreviewLoader previewLoader, + int previewWidth, IconCache iconCache, ComponentName provider, UserHandleCompat user) { final LauncherAppWidgetProviderInfo info = LauncherModel.getProviderInfo(mContext, provider, user); @@ -1000,7 +998,7 @@ public class LauncherBackupHelper implements BackupHelper { } if (info.previewImage != 0) { widget.preview = new Resource(); - Bitmap preview = previewLoader.generateWidgetPreview(info, null); + Bitmap preview = previewLoader.generateWidgetPreview(info, previewWidth, null); ByteArrayOutputStream os = new ByteArrayOutputStream(); if (preview.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) { widget.preview.data = os.toByteArray(); diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 8cedcc572..dcb375928 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -1622,6 +1622,9 @@ public class LauncherModel extends BroadcastReceiver if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); loadAndBindAllApps(); + // Remove entries for packages which changed while the launcher was dead. + LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(); + // Restore the default thread priority after we are done loading items synchronized (mLock) { android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); @@ -3007,8 +3010,7 @@ public class LauncherModel extends BroadcastReceiver if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); mIconCache.updateIconsForPkg(packages[i], mUser); mBgAllAppsList.updatePackage(context, packages[i], mUser); - WidgetPreviewLoader.removePackageFromDb( - mApp.getWidgetPreviewCacheDb(), packages[i]); + mApp.getWidgetCache().removePackage(packages[i], mUser); } break; case OP_REMOVE: @@ -3034,8 +3036,7 @@ public class LauncherModel extends BroadcastReceiver for (int i=0; i= 0 && lp.cellX <= (mCellCountX - 1) && - lp.cellY >= 0 && (lp.cellY <= mCellCountY - 1)) { - // If the horizontal or vertical span is set to -1, it is taken to - // mean that it spans the extent of the CellLayout - if (lp.cellHSpan < 0) lp.cellHSpan = mCellCountX; - if (lp.cellVSpan < 0) lp.cellVSpan = mCellCountY; - - child.setId(childId); - mChildren.addView(child, index, lp); - - return true; - } - return false; - } - - @Override - public void removeAllViewsOnPage() { - mChildren.removeAllViews(); - setLayerType(LAYER_TYPE_NONE, null); - } - - @Override - public void removeViewOnPageAt(int index) { - mChildren.removeViewAt(index); - } - - /** - * Clears all the key listeners for the individual icons. - */ - public void resetChildrenOnKeyListeners() { - int childCount = mChildren.getChildCount(); - for (int j = 0; j < childCount; ++j) { - mChildren.getChildAt(j).setOnKeyListener(null); - } - } - - @Override - public int getPageChildCount() { - return mChildren.getChildCount(); - } - - public PagedViewCellLayoutChildren getChildrenLayout() { - return mChildren; - } - - @Override - public View getChildOnPageAt(int i) { - return mChildren.getChildAt(i); - } - - @Override - public int indexOfChildOnPage(View v) { - return mChildren.indexOfChild(v); - } - - public int getCellCountX() { - return mCellCountX; - } - - public int getCellCountY() { - return mCellCountY; - } - - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); - int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); - - int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); - int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); - - if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { - throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions"); - } - - int numWidthGaps = mCellCountX - 1; - int numHeightGaps = mCellCountY - 1; - - if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) { - int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight(); - int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom(); - int hFreeSpace = hSpace - (mCellCountX * mOriginalCellWidth); - int vFreeSpace = vSpace - (mCellCountY * mOriginalCellHeight); - mWidthGap = numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0; - mHeightGap = numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0; - - mChildren.setGap(mWidthGap, mHeightGap); - } else { - mWidthGap = mOriginalWidthGap; - mHeightGap = mOriginalHeightGap; - } - - // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY - int newWidth = widthSpecSize; - int newHeight = heightSpecSize; - if (widthSpecMode == MeasureSpec.AT_MOST) { - newWidth = getPaddingLeft() + getPaddingRight() + (mCellCountX * mCellWidth) + - ((mCellCountX - 1) * mWidthGap); - newHeight = getPaddingTop() + getPaddingBottom() + (mCellCountY * mCellHeight) + - ((mCellCountY - 1) * mHeightGap); - setMeasuredDimension(newWidth, newHeight); - } - - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - int childWidthMeasureSpec = - MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() - - getPaddingRight(), MeasureSpec.EXACTLY); - int childheightMeasureSpec = - MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() - - getPaddingBottom(), MeasureSpec.EXACTLY); - child.measure(childWidthMeasureSpec, childheightMeasureSpec); - } - - setMeasuredDimension(newWidth, newHeight); - } - - int getContentWidth() { - return getWidthBeforeFirstLayout() + getPaddingLeft() + getPaddingRight(); - } - - int getContentHeight() { - if (mCellCountY > 0) { - return mCellCountY * mCellHeight + (mCellCountY - 1) * Math.max(0, mHeightGap); - } - return 0; - } - - int getWidthBeforeFirstLayout() { - if (mCellCountX > 0) { - return mCellCountX * mCellWidth + (mCellCountX - 1) * Math.max(0, mWidthGap); - } - return 0; - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - int count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - child.layout(getPaddingLeft(), getPaddingTop(), - r - l - getPaddingRight(), b - t - getPaddingBottom()); - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean result = super.onTouchEvent(event); - int count = getPageChildCount(); - if (count > 0) { - // We only intercept the touch if we are tapping in empty space after the final row - View child = getChildOnPageAt(count - 1); - int bottom = child.getBottom(); - int numRows = (int) Math.ceil((float) getPageChildCount() / getCellCountX()); - if (numRows < getCellCountY()) { - // Add a little bit of buffer if there is room for another row - bottom += mCellHeight / 2; - } - result = result || (event.getY() < bottom); - } - return result; - } - - public void enableCenteredContent(boolean enabled) { - mChildren.enableCenteredContent(enabled); - } - - @Override - protected void setChildrenDrawingCacheEnabled(boolean enabled) { - mChildren.setChildrenDrawingCacheEnabled(enabled); - } - - public void setCellCount(int xCount, int yCount) { - mCellCountX = xCount; - mCellCountY = yCount; - requestLayout(); - } - - public void setGap(int widthGap, int heightGap) { - mOriginalWidthGap = mWidthGap = widthGap; - mOriginalHeightGap = mHeightGap = heightGap; - mChildren.setGap(widthGap, heightGap); - } - - public int[] getCellCountForDimensions(int width, int height) { - // Always assume we're working with the smallest span to make sure we - // reserve enough space in both orientations - int smallerSize = Math.min(mCellWidth, mCellHeight); - - // Always round up to next largest cell - int spanX = (width + smallerSize) / smallerSize; - int spanY = (height + smallerSize) / smallerSize; - - return new int[] { spanX, spanY }; - } - - /** - * Start dragging the specified child - * - * @param child The child that is being dragged - */ - void onDragChild(View child) { - PagedViewCellLayout.LayoutParams lp = (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); - lp.isDragging = true; - } - - /** - * Estimates the number of cells that the specified width would take up. - */ - public int estimateCellHSpan(int width) { - // We don't show the next/previous pages any more, so we use the full width, minus the - // padding - int availWidth = width - (getPaddingLeft() + getPaddingRight()); - - // We know that we have to fit N cells with N-1 width gaps, so we just juggle to solve for N - int n = Math.max(1, (availWidth + mWidthGap) / (mCellWidth + mWidthGap)); - - // We don't do anything fancy to determine if we squeeze another row in. - return n; - } - - /** - * Estimates the number of cells that the specified height would take up. - */ - public int estimateCellVSpan(int height) { - // The space for a page is the height - top padding (current page) - bottom padding (current - // page) - int availHeight = height - (getPaddingTop() + getPaddingBottom()); - - // We know that we have to fit N cells with N-1 height gaps, so we juggle to solve for N - int n = Math.max(1, (availHeight + mHeightGap) / (mCellHeight + mHeightGap)); - - // We don't do anything fancy to determine if we squeeze another row in. - return n; - } - - /** Returns an estimated center position of the cell at the specified index */ - public int[] estimateCellPosition(int x, int y) { - return new int[] { - getPaddingLeft() + (x * mCellWidth) + (x * mWidthGap) + (mCellWidth / 2), - getPaddingTop() + (y * mCellHeight) + (y * mHeightGap) + (mCellHeight / 2) - }; - } - - public void calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY) { - mCellCountX = Math.min(maxCellCountX, estimateCellHSpan(width)); - mCellCountY = Math.min(maxCellCountY, estimateCellVSpan(height)); - requestLayout(); - } - - @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - return new PagedViewCellLayout.LayoutParams(getContext(), attrs); - } - - @Override - protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof PagedViewCellLayout.LayoutParams; - } - - @Override - protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - return new PagedViewCellLayout.LayoutParams(p); - } - - public static class LayoutParams extends ViewGroup.MarginLayoutParams { - /** - * Horizontal location of the item in the grid. - */ - @ViewDebug.ExportedProperty - public int cellX; - - /** - * Vertical location of the item in the grid. - */ - @ViewDebug.ExportedProperty - public int cellY; - - /** - * Number of cells spanned horizontally by the item. - */ - @ViewDebug.ExportedProperty - public int cellHSpan; - - /** - * Number of cells spanned vertically by the item. - */ - @ViewDebug.ExportedProperty - public int cellVSpan; - - /** - * Is this item currently being dragged - */ - public boolean isDragging; - - // a data object that you can bind to this layout params - private Object mTag; - - // X coordinate of the view in the layout. - @ViewDebug.ExportedProperty - int x; - // Y coordinate of the view in the layout. - @ViewDebug.ExportedProperty - int y; - - public LayoutParams() { - super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - cellHSpan = 1; - cellVSpan = 1; - } - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - cellHSpan = 1; - cellVSpan = 1; - } - - public LayoutParams(ViewGroup.LayoutParams source) { - super(source); - cellHSpan = 1; - cellVSpan = 1; - } - - public LayoutParams(LayoutParams source) { - super(source); - this.cellX = source.cellX; - this.cellY = source.cellY; - this.cellHSpan = source.cellHSpan; - this.cellVSpan = source.cellVSpan; - } - - public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) { - super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - this.cellX = cellX; - this.cellY = cellY; - this.cellHSpan = cellHSpan; - this.cellVSpan = cellVSpan; - } - - public void setup(Context context, - int cellWidth, int cellHeight, int widthGap, int heightGap, - int hStartPadding, int vStartPadding) { - - final int myCellHSpan = cellHSpan; - final int myCellVSpan = cellVSpan; - final int myCellX = cellX; - final int myCellY = cellY; - - width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - - leftMargin - rightMargin; - height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - - topMargin - bottomMargin; - - if (LauncherAppState.getInstance().isScreenLarge()) { - x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin; - y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin; - } else { - x = myCellX * (cellWidth + widthGap) + leftMargin; - y = myCellY * (cellHeight + heightGap) + topMargin; - } - } - - public Object getTag() { - return mTag; - } - - public void setTag(Object tag) { - mTag = tag; - } - - public String toString() { - return "(" + this.cellX + ", " + this.cellY + ", " + - this.cellHSpan + ", " + this.cellVSpan + ")"; - } - } -} \ No newline at end of file diff --git a/src/com/android/launcher3/PagedViewCellLayoutChildren.java b/src/com/android/launcher3/PagedViewCellLayoutChildren.java deleted file mode 100644 index 84d2b1dd3..000000000 --- a/src/com/android/launcher3/PagedViewCellLayoutChildren.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2010 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.launcher3; - -import android.content.Context; -import android.graphics.Rect; -import android.view.View; -import android.view.ViewGroup; - -/** - * An abstraction of the original CellLayout which supports laying out items - * which span multiple cells into a grid-like layout. Also supports dimming - * to give a preview of its contents. - */ -public class PagedViewCellLayoutChildren extends ViewGroup { - static final String TAG = "PagedViewCellLayout"; - - private boolean mCenterContent; - - private int mCellWidth; - private int mCellHeight; - private int mWidthGap; - private int mHeightGap; - - public PagedViewCellLayoutChildren(Context context) { - super(context); - } - - @Override - public void cancelLongPress() { - super.cancelLongPress(); - - // Cancel long press for all children - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - child.cancelLongPress(); - } - } - - public void setGap(int widthGap, int heightGap) { - mWidthGap = widthGap; - mHeightGap = heightGap; - requestLayout(); - } - - public void setCellDimensions(int width, int height) { - mCellWidth = width; - mCellHeight = height; - requestLayout(); - } - - @Override - public void requestChildFocus(View child, View focused) { - super.requestChildFocus(child, focused); - if (child != null) { - Rect r = new Rect(); - child.getDrawingRect(r); - requestRectangleOnScreen(r); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); - int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); - - int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); - int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); - - if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { - throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions"); - } - - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - PagedViewCellLayout.LayoutParams lp = - (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); - lp.setup(getContext(), - mCellWidth, mCellHeight, mWidthGap, mHeightGap, - getPaddingLeft(), - getPaddingTop()); - - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, - MeasureSpec.EXACTLY); - int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, - MeasureSpec.EXACTLY); - - child.measure(childWidthMeasureSpec, childheightMeasureSpec); - } - - setMeasuredDimension(widthSpecSize, heightSpecSize); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - int count = getChildCount(); - - int offsetX = 0; - if (mCenterContent && count > 0) { - // determine the max width of all the rows and center accordingly - int maxRowX = 0; - int minRowX = Integer.MAX_VALUE; - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - if (child.getVisibility() != GONE) { - PagedViewCellLayout.LayoutParams lp = - (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); - minRowX = Math.min(minRowX, lp.x); - maxRowX = Math.max(maxRowX, lp.x + lp.width); - } - } - int maxRowWidth = maxRowX - minRowX; - offsetX = (getMeasuredWidth() - maxRowWidth) / 2; - } - - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - if (child.getVisibility() != GONE) { - PagedViewCellLayout.LayoutParams lp = - (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); - - int childLeft = offsetX + lp.x; - int childTop = lp.y; - child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height); - } - } - } - - public void enableCenteredContent(boolean enabled) { - mCenterContent = enabled; - } - - @Override - protected void setChildrenDrawingCacheEnabled(boolean enabled) { - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - final View view = getChildAt(i); - view.setDrawingCacheEnabled(enabled); - // Update the drawing caches - if (!view.isHardwareAccelerated()) { - view.buildDrawingCache(true); - } - } - } -} diff --git a/src/com/android/launcher3/PagedViewWidget.java b/src/com/android/launcher3/PagedViewWidget.java index 107069b78..d9ca7be87 100644 --- a/src/com/android/launcher3/PagedViewWidget.java +++ b/src/com/android/launcher3/PagedViewWidget.java @@ -20,35 +20,38 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.Rect; import android.util.AttributeSet; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; +import android.view.View.OnLayoutChangeListener; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest; import com.android.launcher3.compat.AppWidgetManagerCompat; /** * The linear layout used strictly for the widget/wallpaper tab of the customization tray */ -public class PagedViewWidget extends LinearLayout { - static final String TAG = "PagedViewWidgetLayout"; +public class PagedViewWidget extends LinearLayout implements OnLayoutChangeListener { - private static boolean sDeletePreviewsWhenDetachedFromWindow = true; - private static boolean sRecyclePreviewsWhenDetachedFromWindow = true; + private static PagedViewWidget sShortpressTarget = null; - private String mDimensionsFormatString; - CheckForShortPress mPendingCheckForShortPress = null; - ShortPressListener mShortPressListener = null; - boolean mShortPressTriggered = false; - static PagedViewWidget sShortpressTarget = null; - boolean mIsAppWidget; private final Rect mOriginalImagePadding = new Rect(); + + private String mDimensionsFormatString; + private CheckForShortPress mPendingCheckForShortPress = null; + private ShortPressListener mShortPressListener = null; + private boolean mShortPressTriggered = false; + private boolean mIsAppWidget; private Object mInfo; + private WidgetPreviewLoader mWidgetPreviewLoader; + private PreviewLoadRequest mActiveRequest; public PagedViewWidget(Context context) { this(context, null); @@ -92,29 +95,24 @@ public class PagedViewWidget extends LinearLayout { } } - public static void setDeletePreviewsWhenDetachedFromWindow(boolean value) { - sDeletePreviewsWhenDetachedFromWindow = value; - } - - public static void setRecyclePreviewsWhenDetachedFromWindow(boolean value) { - sRecyclePreviewsWhenDetachedFromWindow = value; - } - @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); + deletePreview(true); + } - if (sDeletePreviewsWhenDetachedFromWindow) { + public void deletePreview(boolean recycleImage) { + if (recycleImage) { final ImageView image = (ImageView) findViewById(R.id.widget_preview); if (image != null) { - FastBitmapDrawable preview = (FastBitmapDrawable) image.getDrawable(); - if (sRecyclePreviewsWhenDetachedFromWindow && - mInfo != null && preview != null && preview.getBitmap() != null) { - mWidgetPreviewLoader.recycleBitmap(mInfo, preview.getBitmap()); - } image.setImageDrawable(null); } } + + if (mActiveRequest != null) { + mActiveRequest.cancel(recycleImage); + mActiveRequest = null; + } } public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info, @@ -161,7 +159,8 @@ public class PagedViewWidget extends LinearLayout { return maxSize; } - void applyPreview(FastBitmapDrawable preview, int index) { + public void applyPreview(Bitmap bitmap) { + FastBitmapDrawable preview = new FastBitmapDrawable(bitmap); final PagedViewWidgetImageView image = (PagedViewWidgetImageView) findViewById(R.id.widget_preview); if (preview != null) { @@ -259,4 +258,38 @@ public class PagedViewWidget extends LinearLayout { // we just always mark the touch event as handled. return true; } + + public void ensurePreview() { + if (mActiveRequest != null) { + return; + } + int[] size = getPreviewSize(); + + if (size[0] <= 0 || size[1] <= 0) { + addOnLayoutChangeListener(this); + return; + } + Bitmap[] immediateResult = new Bitmap[1]; + mActiveRequest = mWidgetPreviewLoader.getPreview(mInfo, size[0], size[1], this, + immediateResult); + if (immediateResult[0] != null) { + applyPreview(immediateResult[0]); + } + } + + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, + int oldTop, int oldRight, int oldBottom) { + removeOnLayoutChangeListener(this); + ensurePreview(); + } + + public int getActualItemWidth() { + ItemInfo info = (ItemInfo) getTag(); + int[] size = getPreviewSize(); + int cellWidth = LauncherAppState.getInstance() + .getDynamicGrid().getDeviceProfile().cellWidthPx; + + return Math.min(size[0], info.spanX * cellWidth); + } } diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 497b43874..22677c8ea 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -50,6 +50,8 @@ import android.util.SparseArray; import android.view.View; import android.widget.Toast; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; @@ -555,6 +557,25 @@ public final class Utilities { return defaultWidgetForSearchPackage; } + /** + * Compresses the bitmap to a byte array for serialization. + */ + public static byte[] flattenBitmap(Bitmap bitmap) { + // Try go guesstimate how much space the icon will take when serialized + // to avoid unnecessary allocations/copies during the write. + int size = bitmap.getWidth() * bitmap.getHeight() * 4; + ByteArrayOutputStream out = new ByteArrayOutputStream(size); + try { + bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); + out.flush(); + out.close(); + return out.toByteArray(); + } catch (IOException e) { + Log.w(TAG, "Could not write bitmap"); + return null; + } + } + public static final Comparator RANK_COMPARATOR = new Comparator() { @Override diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index 0a5f0af4e..1043e2ee0 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -1,18 +1,16 @@ package com.android.launcher3; -import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; -import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.database.Cursor; -import android.database.sqlite.SQLiteCantOpenDatabaseException; +import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteDiskIOException; import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteReadOnlyDatabaseException; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; @@ -26,400 +24,332 @@ import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; -import android.os.Build; import android.util.Log; +import android.util.LongSparseArray; import com.android.launcher3.compat.AppWidgetManagerCompat; +import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.Thunk; -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.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.List; +import java.util.Set; +import java.util.WeakHashMap; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; public class WidgetPreviewLoader { 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; - @Thunk static final HashSet sInvalidPackages = new HashSet(); - private final HashMap> mLoadedPreviews = new HashMap<>(); - private final ArrayList> mUnusedBitmaps = new ArrayList<>(); - - private final int mAppIconSize; - private final int mCellWidth; + private final HashMap mPackageVersions = new HashMap<>(); + private final HashMap> mLoadedPreviews = new HashMap<>(); + private Set mUnusedBitmaps = Collections.newSetFromMap(new WeakHashMap()); private final Context mContext; private final IconCache mIconCache; + private final UserManagerCompat mUserManager; private final AppWidgetManagerCompat mManager; - - private int mPreviewBitmapWidth; - private int mPreviewBitmapHeight; - private String mSize; - - private String mCachedSelectQuery; - private CacheDb mDb; + private final CacheDb mDb; private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); - public WidgetPreviewLoader(Context context) { - LauncherAppState app = LauncherAppState.getInstance(); - DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); - + public WidgetPreviewLoader(Context context, IconCache iconCache) { mContext = context; - mAppIconSize = grid.iconSizePx; - mCellWidth = grid.cellWidthPx; - - mIconCache = app.getIconCache(); + mIconCache = iconCache; mManager = AppWidgetManagerCompat.getInstance(context); - mDb = app.getWidgetPreviewCacheDb(); - - SharedPreferences sp = context.getSharedPreferences( - LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE); - final String lastVersionName = sp.getString(ANDROID_INCREMENTAL_VERSION_NAME_KEY, null); - final String versionName = android.os.Build.VERSION.INCREMENTAL; - final boolean isLollipopOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; - if (!versionName.equals(lastVersionName)) { - try { - // clear all the previews whenever the system version changes, to ensure that - // previews are up-to-date for any apps that might have been updated with the system - clearDb(); - } catch (SQLiteReadOnlyDatabaseException e) { - if (isLollipopOrGreater) { - // Workaround for Bug. 18554839, if we fail to clear the db due to the read-only - // issue, then ignore this error and leave the old previews - } else { - throw e; - } - } finally { - SharedPreferences.Editor editor = sp.edit(); - editor.putString(ANDROID_INCREMENTAL_VERSION_NAME_KEY, versionName); - editor.commit(); - } - } + mUserManager = UserManagerCompat.getInstance(context); + mDb = new CacheDb(context); } - public void recreateDb() { - LauncherAppState app = LauncherAppState.getInstance(); - app.recreateWidgetPreviewDb(); - mDb = app.getWidgetPreviewCacheDb(); - } - - public void setPreviewSize(int previewWidth, int previewHeight) { - mPreviewBitmapWidth = previewWidth; - mPreviewBitmapHeight = previewHeight; - mSize = previewWidth + "x" + previewHeight; - } - - public Bitmap getPreview(final Object o) { - final String name = getObjectName(o); - final String packageName = getObjectPackage(o); - // check if the package is valid - synchronized(sInvalidPackages) { - boolean packageValid = !sInvalidPackages.contains(packageName); - if (!packageValid) { - return null; - } - } - synchronized(mLoadedPreviews) { - // check if it exists in our existing cache - if (mLoadedPreviews.containsKey(name)) { - WeakReference bitmapReference = mLoadedPreviews.get(name); - Bitmap bitmap = bitmapReference.get(); - if (bitmap != null) { - return bitmap; - } - } - } - - Bitmap unusedBitmap = null; - synchronized(mUnusedBitmaps) { - // not in cache; we need to load it from the db - 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) { - unusedBitmap = Bitmap.createBitmap(mPreviewBitmapWidth, mPreviewBitmapHeight, - Bitmap.Config.ARGB_8888); - } - Bitmap preview = readFromDb(name, unusedBitmap); - - if (preview != null) { - synchronized(mLoadedPreviews) { - mLoadedPreviews.put(name, new WeakReference(preview)); - } - return preview; - } else { - // it's not in the db... we need to generate it - final Bitmap generatedPreview = generatePreview(o, unusedBitmap); - preview = generatedPreview; - if (preview != unusedBitmap) { - throw new RuntimeException("generatePreview is not recycling the bitmap " + o); - } - - synchronized(mLoadedPreviews) { - mLoadedPreviews.put(name, new WeakReference(preview)); - } - - // write to db on a thread pool... this can be done lazily and improves the performance - // of the first time widget previews are loaded - new AsyncTask() { - public Void doInBackground(Void ... args) { - writeToDb(o, generatedPreview); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); - - return preview; - } - } + /** + * Generates the widget preview on {@link AsyncTask#THREAD_POOL_EXECUTOR}. Must be + * called on UI thread + * + * @param o either {@link LauncherAppWidgetProviderInfo} or {@link ResolveInfo} + * @param immediateResult A bitmap array of size 1. If the result is already cached, it is + * set to the final result. + * @return a request id which can be used to cancel the request. + */ + public PreviewLoadRequest getPreview(final Object o, int previewWidth, int previewHeight, + PagedViewWidget caller, Bitmap[] immediateResult) { + String size = previewWidth + "x" + previewHeight; + WidgetCacheKey key = getObjectKey(o, size); - public void recycleBitmap(Object o, Bitmap bitmapToRecycle) { - String name = getObjectName(o); + // Check if we have the preview loaded or not. synchronized (mLoadedPreviews) { - if (mLoadedPreviews.containsKey(name)) { - Bitmap b = mLoadedPreviews.get(name).get(); - if (b == bitmapToRecycle) { - mLoadedPreviews.remove(name); - if (bitmapToRecycle.isMutable()) { - synchronized (mUnusedBitmaps) { - mUnusedBitmaps.add(new SoftReference(b)); - } - } - } else { - throw new RuntimeException("Bitmap passed in doesn't match up"); - } + WeakReference ref = mLoadedPreviews.get(key); + if (ref != null && ref.get() != null) { + immediateResult[0] = ref.get(); + return new PreviewLoadRequest(null, key); } } + + PreviewLoadTask task = new PreviewLoadTask(key, o, previewWidth, previewHeight, caller); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + return new PreviewLoadRequest(task, key); } - static class CacheDb extends SQLiteOpenHelper { - final static int DB_VERSION = 2; - final static String TABLE_NAME = "shortcut_and_widget_previews"; - final static String COLUMN_NAME = "name"; - final static String COLUMN_SIZE = "size"; - final static String COLUMN_PREVIEW_BITMAP = "preview_bitmap"; - Context mContext; + /** + * The DB holds the generated previews for various components. Previews can also have different + * sizes (landscape vs portrait). + */ + private static class CacheDb extends SQLiteOpenHelper { + private static final int DB_VERSION = 3; + + private static final String TABLE_NAME = "shortcut_and_widget_previews"; + private static final String COLUMN_COMPONENT = "componentName"; + private static final String COLUMN_USER = "profileId"; + private static final String COLUMN_SIZE = "size"; + private static final String COLUMN_PACKAGE = "packageName"; + private static final String COLUMN_LAST_UPDATED = "lastUpdated"; + private static final String COLUMN_VERSION = "version"; + private static final String COLUMN_PREVIEW_BITMAP = "preview_bitmap"; public CacheDb(Context context) { - super(context, new File(context.getCacheDir(), - LauncherFiles.WIDGET_PREVIEWS_DB).getPath(), null, DB_VERSION); - // Store the context for later use - mContext = context; + super(context, LauncherFiles.WIDGET_PREVIEWS_DB, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase database) { database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" + - COLUMN_NAME + " TEXT NOT NULL, " + + COLUMN_COMPONENT + " TEXT NOT NULL, " + + COLUMN_USER + " INTEGER NOT NULL, " + COLUMN_SIZE + " TEXT NOT NULL, " + - COLUMN_PREVIEW_BITMAP + " BLOB NOT NULL, " + - "PRIMARY KEY (" + COLUMN_NAME + ", " + COLUMN_SIZE + ") " + + COLUMN_PACKAGE + " TEXT NOT NULL, " + + COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " + + COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " + + COLUMN_PREVIEW_BITMAP + " BLOB, " + + "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ", " + COLUMN_SIZE + ") " + ");"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion != newVersion) { - // Delete all the records; they'll be repopulated as this is a cache - db.execSQL("DELETE FROM " + TABLE_NAME); + clearDB(db); } } - } - private static final String WIDGET_PREFIX = "Widget:"; - private static final String SHORTCUT_PREFIX = "Shortcut:"; + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (oldVersion != newVersion) { + clearDB(db); + } + } - private static String getObjectName(Object o) { - // should cache the string builder - StringBuilder sb = new StringBuilder(); - String output; - if (o instanceof AppWidgetProviderInfo) { - sb.append(WIDGET_PREFIX); - sb.append(((AppWidgetProviderInfo) o).toString()); - output = sb.toString(); - sb.setLength(0); - } else { - sb.append(SHORTCUT_PREFIX); - ResolveInfo info = (ResolveInfo) o; - sb.append(new ComponentName(info.activityInfo.packageName, - info.activityInfo.name).flattenToString()); - output = sb.toString(); - sb.setLength(0); + private void clearDB(SQLiteDatabase db) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); + onCreate(db); } - return output; } - private String getObjectPackage(Object o) { - if (o instanceof AppWidgetProviderInfo) { - return ((AppWidgetProviderInfo) o).provider.getPackageName(); + private WidgetCacheKey getObjectKey(Object o, String size) { + // should cache the string builder + if (o instanceof LauncherAppWidgetProviderInfo) { + LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) o; + return new WidgetCacheKey(info.provider, mManager.getUser(info), size); } else { ResolveInfo info = (ResolveInfo) o; - return info.activityInfo.packageName; + return new WidgetCacheKey( + new ComponentName(info.activityInfo.packageName, info.activityInfo.name), + UserHandleCompat.myUserHandle(), size); } } - @Thunk void writeToDb(Object o, Bitmap preview) { - String name = getObjectName(o); - SQLiteDatabase db = mDb.getWritableDatabase(); + @Thunk void writeToDb(WidgetCacheKey key, long[] versions, Bitmap preview) { ContentValues values = new ContentValues(); + values.put(CacheDb.COLUMN_COMPONENT, key.componentName.flattenToShortString()); + values.put(CacheDb.COLUMN_USER, mUserManager.getSerialNumberForUser(key.user)); + values.put(CacheDb.COLUMN_SIZE, key.size); + values.put(CacheDb.COLUMN_PACKAGE, key.componentName.getPackageName()); + values.put(CacheDb.COLUMN_VERSION, versions[0]); + values.put(CacheDb.COLUMN_LAST_UPDATED, versions[1]); + values.put(CacheDb.COLUMN_PREVIEW_BITMAP, Utilities.flattenBitmap(preview)); - values.put(CacheDb.COLUMN_NAME, name); - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - preview.compress(Bitmap.CompressFormat.PNG, 100, stream); - values.put(CacheDb.COLUMN_PREVIEW_BITMAP, stream.toByteArray()); - values.put(CacheDb.COLUMN_SIZE, mSize); try { - db.insert(CacheDb.TABLE_NAME, null, values); - } catch (SQLiteDiskIOException e) { - recreateDb(); - } catch (SQLiteCantOpenDatabaseException e) { - dumpOpenFiles(); - throw e; + mDb.getWritableDatabase().insertWithOnConflict(CacheDb.TABLE_NAME, null, values, + SQLiteDatabase.CONFLICT_REPLACE); + } catch (SQLException e) { + Log.e(TAG, "Error saving image to DB", e); } } - private void clearDb() { - SQLiteDatabase db = mDb.getWritableDatabase(); - // Delete everything + public void removePackage(String packageName, UserHandleCompat user) { + removePackage(packageName, user, mUserManager.getSerialNumberForUser(user)); + } + + private void removePackage(String packageName, UserHandleCompat user, long userSerial) { + synchronized(mPackageVersions) { + mPackageVersions.remove(packageName); + } + + synchronized (mLoadedPreviews) { + Set keysToRemove = new HashSet<>(); + for (WidgetCacheKey key : mLoadedPreviews.keySet()) { + if (key.componentName.getPackageName().equals(packageName) && key.user.equals(user)) { + keysToRemove.add(key); + } + } + + for (WidgetCacheKey key : keysToRemove) { + WeakReference req = mLoadedPreviews.remove(key); + if (req != null && req.get() != null) { + mUnusedBitmaps.add(req.get()); + } + } + } + try { - db.delete(CacheDb.TABLE_NAME, null, null); - } catch (SQLiteDiskIOException e) { - } catch (SQLiteCantOpenDatabaseException e) { - dumpOpenFiles(); - throw e; + mDb.getWritableDatabase().delete(CacheDb.TABLE_NAME, + CacheDb.COLUMN_PACKAGE + " = ? AND " + CacheDb.COLUMN_USER + " = ?", + new String[] {packageName, Long.toString(userSerial)}); + } catch (SQLException e) { + Log.e(TAG, "Unable to delete items from DB", e); } } - public static void removePackageFromDb(final CacheDb cacheDb, final String packageName) { - synchronized(sInvalidPackages) { - sInvalidPackages.add(packageName); + /** + * Updates the persistent DB: + * 1. Any preview generated for an old package version is removed + * 2. Any preview for an absent package is removed + * This ensures that we remove entries for packages which changed while the launcher was dead. + */ + public void removeObsoletePreviews() { + LongSparseArray userIdCache = new LongSparseArray<>(); + LongSparseArray> validPackages = new LongSparseArray<>(); + + for (Object obj : LauncherModel.getSortedWidgetsAndShortcuts(mContext, false)) { + final UserHandleCompat user; + final String pkg; + if (obj instanceof ResolveInfo) { + user = UserHandleCompat.myUserHandle(); + pkg = ((ResolveInfo) obj).activityInfo.packageName; + } else { + LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) obj; + user = mManager.getUser(info); + pkg = info.provider.getPackageName(); + } + + int userIdIndex = userIdCache.indexOfValue(user); + final long userId; + if (userIdIndex < 0) { + userId = mUserManager.getSerialNumberForUser(user); + userIdCache.put(userId, user); + } else { + userId = userIdCache.keyAt(userIdIndex); + } + + HashSet packages = validPackages.get(userId); + if (packages == null) { + packages = new HashSet<>(); + validPackages.put(userId, packages); + } + packages.add(pkg); } - new AsyncTask() { - public Void doInBackground(Void ... args) { - SQLiteDatabase db = cacheDb.getWritableDatabase(); - try { - db.delete(CacheDb.TABLE_NAME, - CacheDb.COLUMN_NAME + " LIKE ? OR " + - CacheDb.COLUMN_NAME + " LIKE ?", // SELECT query - new String[] { - WIDGET_PREFIX + packageName + "/%", - SHORTCUT_PREFIX + packageName + "/%" - } // args to SELECT query - ); - } catch (SQLiteDiskIOException e) { - } catch (SQLiteCantOpenDatabaseException e) { - dumpOpenFiles(); - throw e; + + LongSparseArray> packagesToDelete = new LongSparseArray<>(); + Cursor c = null; + try { + c = mDb.getReadableDatabase().query(CacheDb.TABLE_NAME, + new String[] {CacheDb.COLUMN_USER, CacheDb.COLUMN_PACKAGE, + CacheDb.COLUMN_LAST_UPDATED, CacheDb.COLUMN_VERSION}, + null, null, null, null, null); + while (c.moveToNext()) { + long userId = c.getLong(0); + String pkg = c.getString(1); + long lastUpdated = c.getLong(2); + long version = c.getLong(3); + + HashSet packages = validPackages.get(userId); + if (packages != null && packages.contains(pkg)) { + long[] versions = getPackageVersion(pkg); + if (versions[0] == version && versions[1] == lastUpdated) { + // Every thing checks out + continue; + } } - synchronized(sInvalidPackages) { - sInvalidPackages.remove(packageName); + + // We need to delete this package. + packages = packagesToDelete.get(userId); + if (packages == null) { + packages = new HashSet<>(); + packagesToDelete.put(userId, packages); } - return null; + packages.add(pkg); } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); - } - private static void removeItemFromDb(final CacheDb cacheDb, final String objectName) { - new AsyncTask() { - public Void doInBackground(Void ... args) { - SQLiteDatabase db = cacheDb.getWritableDatabase(); - try { - db.delete(CacheDb.TABLE_NAME, - CacheDb.COLUMN_NAME + " = ? ", // SELECT query - new String[] { objectName }); // args to SELECT query - } catch (SQLiteDiskIOException e) { - } catch (SQLiteCantOpenDatabaseException e) { - dumpOpenFiles(); - throw e; + for (int i = 0; i < packagesToDelete.size(); i++) { + long userId = packagesToDelete.keyAt(i); + UserHandleCompat user = mUserManager.getUserForSerialNumber(userId); + for (String pkg : packagesToDelete.valueAt(i)) { + removePackage(pkg, user, userId); } - return null; } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); + } catch (SQLException e) { + Log.e(TAG, "Error updatating widget previews", e); + } finally { + if (c != null) { + c.close(); + } + } } - private Bitmap readFromDb(String name, Bitmap b) { - if (mCachedSelectQuery == null) { - mCachedSelectQuery = CacheDb.COLUMN_NAME + " = ? AND " + - CacheDb.COLUMN_SIZE + " = ?"; - } - SQLiteDatabase db = mDb.getReadableDatabase(); - Cursor result; + private Bitmap readFromDb(WidgetCacheKey key, Bitmap recycle) { + Cursor cursor = null; try { - result = db.query(CacheDb.TABLE_NAME, - new String[] { CacheDb.COLUMN_PREVIEW_BITMAP }, // cols to return - mCachedSelectQuery, // select query - new String[] { name, mSize }, // args to select query - null, - null, - null, - null); - } catch (SQLiteDiskIOException e) { - recreateDb(); - return null; - } catch (SQLiteCantOpenDatabaseException e) { - dumpOpenFiles(); - throw e; - } - if (result.getCount() > 0) { - result.moveToFirst(); - byte[] blob = result.getBlob(0); - result.close(); - final BitmapFactory.Options opts = new BitmapFactory.Options(); - opts.inBitmap = b; - opts.inSampleSize = 1; - try { - return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts); - } catch (IllegalArgumentException e) { - removeItemFromDb(mDb, name); - return null; + cursor = mDb.getReadableDatabase().query( + CacheDb.TABLE_NAME, + new String[] { CacheDb.COLUMN_PREVIEW_BITMAP }, + CacheDb.COLUMN_COMPONENT + " = ? AND " + CacheDb.COLUMN_USER + " = ? AND " + CacheDb.COLUMN_SIZE + " = ?", + new String[] { + key.componentName.flattenToString(), + Long.toString(mUserManager.getSerialNumberForUser(key.user)), + key.size + }, + null, null, null); + if (cursor.moveToNext()) { + byte[] blob = cursor.getBlob(0); + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inBitmap = recycle; + try { + return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts); + } catch (Exception e) { + return null; + } + } + } catch (SQLException e) { + Log.w(TAG, "Error loading preview from DB", e); + } finally { + if (cursor != null) { + cursor.close(); } - } else { - result.close(); - return null; } + return null; } - private Bitmap generatePreview(Object info, Bitmap preview) { - if (preview != null && - (preview.getWidth() != mPreviewBitmapWidth || - preview.getHeight() != mPreviewBitmapHeight)) { - throw new RuntimeException("Improperly sized bitmap passed as argument"); - } + private Bitmap generatePreview(Object info, Bitmap recycle, int previewWidth, int previewHeight) { if (info instanceof LauncherAppWidgetProviderInfo) { - return generateWidgetPreview((LauncherAppWidgetProviderInfo) info, preview); + return generateWidgetPreview((LauncherAppWidgetProviderInfo) info, previewWidth, recycle); } else { return generateShortcutPreview( - (ResolveInfo) info, mPreviewBitmapWidth, mPreviewBitmapHeight, preview); + (ResolveInfo) info, previewWidth, previewHeight, recycle); } } - public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info, Bitmap preview) { - int maxWidth = maxWidthForWidgetPreview(info.spanX); + public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info, + int previewWidth, Bitmap preview) { + int maxWidth = Math.min(previewWidth, info.spanX + * LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile().cellWidthPx); return generateWidgetPreview(info, maxWidth, preview, null); } - public int maxWidthForWidgetPreview(int spanX) { - return Math.min(mPreviewBitmapWidth, spanX * mCellWidth); - } - public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info, int maxPreviewWidth, Bitmap preview, int[] preScaledWidthOut) { // Load the preview image if possible @@ -488,6 +418,8 @@ public class WidgetPreviewLoader { } else { final Paint p = new Paint(); p.setFilterBitmap(true); + int appIconSize = LauncherAppState.getInstance().getDynamicGrid() + .getDeviceProfile().iconSizePx; // draw the spanX x spanY tiles final Rect src = new Rect(0, 0, tileBitmap.getWidth(), tileBitmap.getHeight()); @@ -507,18 +439,18 @@ public class WidgetPreviewLoader { // Draw the icon in the top left corner // TODO: use top right for RTL - int minOffset = (int) (mAppIconSize * WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE); + int minOffset = (int) (appIconSize * WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE); int smallestSide = Math.min(previewWidth, previewHeight); - float iconScale = Math.min((float) smallestSide / (mAppIconSize + 2 * minOffset), scale); + float iconScale = Math.min((float) smallestSide / (appIconSize + 2 * minOffset), scale); try { Drawable icon = mutateOnMainThread(mManager.loadIcon(info, mIconCache)); if (icon != null) { - int hoffset = (int) ((tileW - mAppIconSize * iconScale) / 2) + x; - int yoffset = (int) ((tileH - mAppIconSize * iconScale) / 2); + int hoffset = (int) ((tileW - appIconSize * iconScale) / 2) + x; + int yoffset = (int) ((tileH - appIconSize * iconScale) / 2); icon.setBounds(hoffset, yoffset, - hoffset + (int) (mAppIconSize * iconScale), - yoffset + (int) (mAppIconSize * iconScale)); + hoffset + (int) (appIconSize * iconScale), + yoffset + (int) (appIconSize * iconScale)); icon.draw(c); } } catch (Resources.NotFoundException e) { } @@ -561,9 +493,11 @@ public class WidgetPreviewLoader { // Draw the final icon at top left corner. // TODO: use top right for RTL + int appIconSize = LauncherAppState.getInstance().getDynamicGrid() + .getDeviceProfile().iconSizePx; icon.setAlpha(255); icon.setColorFilter(null); - icon.setBounds(0, 0, mAppIconSize, mAppIconSize); + icon.setBounds(0, 0, appIconSize, appIconSize); icon.draw(c); c.setBitmap(null); @@ -586,82 +520,144 @@ public class WidgetPreviewLoader { } } - 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. + * @return an array of containing versionCode and lastUpdatedTime for the package. */ - @Thunk 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 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 files = new HashSet(); - 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; + private long[] getPackageVersion(String packageName) { + synchronized (mPackageVersions) { + long[] versions = mPackageVersions.get(packageName); + if (versions == null) { + versions = new long[2]; 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//fd/. - type = types.indexOf(TYPE_NON_FS); - } - count[type]++; - total++; - if (files.contains(resolved)) { - duplicates[type]++; + PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, 0); + versions[0] = info.versionCode; + versions[1] = info.lastUpdateTime; + } catch (NameNotFoundException e) { + Log.e(TAG, "PackageInfo not found", e); + } + mPackageVersions.put(packageName, versions); + } + return versions; + } + } + + /** + * A request Id which can be used by the client to cancel any request. + */ + public class PreviewLoadRequest { + + private final PreviewLoadTask mTask; + private final WidgetCacheKey mKey; + + public PreviewLoadRequest(PreviewLoadTask task, WidgetCacheKey key) { + mTask = task; + mKey = key; + } + + public void cancel(boolean recycleImage) { + if (mTask != null) { + mTask.cancel(true); + } + + if (recycleImage) { + synchronized(mLoadedPreviews) { + WeakReference result = mLoadedPreviews.remove(mKey); + if (result != null && result.get() != null) { + mUnusedBitmaps.add(result.get()); } - files.add(resolved); - if (total % SAMPLE_RATE == 0) { - Log.i(TAG, " fd " + i + ": " + resolved - + " (" + types.get(type) + ")"); + } + } + } + } + + public class PreviewLoadTask extends AsyncTask { + + private final WidgetCacheKey mKey; + private final Object mInfo; + private final int mPreviewHeight; + private final int mPreviewWidth; + private final PagedViewWidget mCaller; + + PreviewLoadTask(WidgetCacheKey key, Object info, int previewWidth, + int previewHeight, PagedViewWidget caller) { + mKey = key; + mInfo = info; + mPreviewHeight = previewHeight; + mPreviewWidth = previewWidth; + mCaller = caller; + } + + + @Override + protected Bitmap doInBackground(Void... params) { + Bitmap unusedBitmap = null; + synchronized (mUnusedBitmaps) { + // Check if we can use a bitmap + for (Bitmap candidate : mUnusedBitmaps) { + if (candidate != null && candidate.isMutable() && + candidate.getWidth() == mPreviewWidth && + candidate.getHeight() == mPreviewHeight) { + unusedBitmap = candidate; + break; } - } catch (IOException e) { - // Ignoring exceptions for non-existing file descriptors. + } + + if (unusedBitmap == null) { + unusedBitmap = Bitmap.createBitmap(mPreviewWidth, mPreviewHeight, Config.ARGB_8888); + } else { + mUnusedBitmaps.remove(unusedBitmap); + } + } + + if (isCancelled()) { + return null; + } + Bitmap preview = readFromDb(mKey, unusedBitmap); + if (!isCancelled() && preview == null) { + // Fetch the version info before we generate the preview, so that, in-case the + // app was updated while we are generating the preview, we use the old version info, + // which would gets re-written next time. + long[] versions = getPackageVersion(mKey.componentName.getPackageName()); + + // it's not in the db... we need to generate it + preview = generatePreview(mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight); + + if (!isCancelled()) { + writeToDb(mKey, versions, preview); } } - 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])); + + return preview; + } + + @Override + protected void onPostExecute(Bitmap result) { + synchronized(mLoadedPreviews) { + mLoadedPreviews.put(mKey, new WeakReference(result)); } - } 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); + + mCaller.applyPreview(result); + } + } + + private static final class WidgetCacheKey extends ComponentKey { + + // TODO: remove dependency on size + private final String size; + + public WidgetCacheKey(ComponentName componentName, UserHandleCompat user, String size) { + super(componentName, user); + this.size = size; + } + + @Override + public int hashCode() { + return super.hashCode() ^ size.hashCode(); + } + + @Override + public boolean equals(Object o) { + return super.equals(o) && ((WidgetCacheKey) o).size.equals(size); } } } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 37265fe47..c274e4d4e 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -366,13 +366,12 @@ public class Workspace extends SmoothPagedView // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each // dimension if unsuccessful - public int[] estimateItemSize(int hSpan, int vSpan, - ItemInfo itemInfo, boolean springLoaded) { + public int[] estimateItemSize(ItemInfo itemInfo, boolean springLoaded) { int[] size = new int[2]; if (getChildCount() > 0) { // Use the first non-custom page to estimate the child position CellLayout cl = (CellLayout) getChildAt(numCustomPages()); - Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan); + Rect r = estimateItemPosition(cl, itemInfo, 0, 0, itemInfo.spanX, itemInfo.spanY); size[0] = r.width(); size[1] = r.height(); if (springLoaded) { @@ -2065,7 +2064,7 @@ public class Workspace extends SmoothPagedView } public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) { - int[] size = estimateItemSize(info.spanX, info.spanY, info, false); + int[] size = estimateItemSize(info, false); // The outline is used to visualize where the item will land if dropped mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha); @@ -4032,8 +4031,7 @@ public class Workspace extends SmoothPagedView } public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) { - int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX, - widgetInfo.spanY, widgetInfo, false); + int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo, false); int visibility = layout.getVisibility(); layout.setVisibility(VISIBLE); -- cgit v1.2.3