From 34b6527cefd36fbd5da78464ce9771e379158552 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 11 Mar 2015 16:56:52 -0700 Subject: Lazy loading high res icons > Loading low-res icons for icons which are not visible on the homescreen. Change-Id: I8ac7bf09f6030ed554cb60a4cd402f3f36ffe12b --- src/com/android/launcher3/AllAppsList.java | 2 +- src/com/android/launcher3/AppInfo.java | 7 +- src/com/android/launcher3/BubbleTextView.java | 45 +++++++++ src/com/android/launcher3/Folder.java | 5 + src/com/android/launcher3/FolderIcon.java | 2 +- src/com/android/launcher3/FolderPagedView.java | 22 +++++ src/com/android/launcher3/IconCache.java | 125 ++++++++++++++++++++----- src/com/android/launcher3/LauncherModel.java | 22 +++-- src/com/android/launcher3/ShortcutInfo.java | 12 ++- src/com/android/launcher3/Workspace.java | 3 +- 10 files changed, 209 insertions(+), 36 deletions(-) diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index 5ed7a629a..dd646bb22 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -148,7 +148,7 @@ class AllAppsList { if (applicationInfo == null) { add(new AppInfo(context, info, user, mIconCache)); } else { - mIconCache.getTitleAndIcon(applicationInfo, info); + mIconCache.getTitleAndIcon(applicationInfo, info, true /* useLowResIcon */); modified.add(applicationInfo); } } diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java index 455c6d16d..a1391b232 100644 --- a/src/com/android/launcher3/AppInfo.java +++ b/src/com/android/launcher3/AppInfo.java @@ -45,6 +45,11 @@ public class AppInfo extends ItemInfo { */ Bitmap iconBitmap; + /** + * Indicates whether we're using a low res icon + */ + boolean usingLowResIcon; + /** * The time at which the app was first installed. */ @@ -79,7 +84,7 @@ public class AppInfo extends ItemInfo { flags = initFlags(info); firstInstallTime = info.getFirstInstallTime(); - iconCache.getTitleAndIcon(this, info); + iconCache.getTitleAndIcon(this, info, true /* useLowResIcon */); intent = makeLaunchIntent(context, info, user); this.user = user; } diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 8ef234bb0..50549cad0 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -34,6 +34,8 @@ import android.view.MotionEvent; import android.view.ViewConfiguration; import android.widget.TextView; +import com.android.launcher3.IconCache.IconLoadRequest; + /** * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan * because we want to make the bubble taller than the text and TextView's clip is @@ -74,6 +76,8 @@ public class BubbleTextView extends TextView { private boolean mStayPressed; private boolean mIgnorePressedStateChange; + private IconLoadRequest mIconLoadRequest; + public BubbleTextView(Context context) { this(context, null, 0); } @@ -163,6 +167,9 @@ public class BubbleTextView extends TextView { } // We don't need to check the info since it's not a ShortcutInfo super.setTag(info); + + // Verify high res immediately + verifyHighRes(); } @Override @@ -450,4 +457,42 @@ public class BubbleTextView extends TextView { } return icon; } + + /** + * Applies the item info if it is same as what the view is pointing to currently. + */ + public void reapplyItemInfo(final ItemInfo info) { + if (getTag() == info) { + mIconLoadRequest = null; + if (info instanceof AppInfo) { + applyFromApplicationInfo((AppInfo) info); + } else if (info instanceof ShortcutInfo) { + applyFromShortcutInfo((ShortcutInfo) info, + LauncherAppState.getInstance().getIconCache(), false); + } + } + } + + /** + * Verifies that the current icon is high-res otherwise posts a request to load the icon. + */ + public void verifyHighRes() { + if (mIconLoadRequest != null) { + mIconLoadRequest.cancel(); + mIconLoadRequest = null; + } + if (getTag() instanceof AppInfo) { + AppInfo info = (AppInfo) getTag(); + if (info.usingLowResIcon) { + mIconLoadRequest = LauncherAppState.getInstance().getIconCache() + .updateIconInBackground(BubbleTextView.this, info); + } + } else if (getTag() instanceof ShortcutInfo) { + ShortcutInfo info = (ShortcutInfo) getTag(); + if (info.usingLowResIcon) { + mIconLoadRequest = LauncherAppState.getInstance().getIconCache() + .updateIconInBackground(BubbleTextView.this, info); + } + } + } } diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java index deb94ca28..c7c6571ef 100644 --- a/src/com/android/launcher3/Folder.java +++ b/src/com/android/launcher3/Folder.java @@ -536,6 +536,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList if (mDragController.isDragging()) { mDragController.forceTouchMove(); } + + if (ALLOW_FOLDER_SCROLL) { + FolderPagedView pages = (FolderPagedView) mContent; + pages.verifyVisibleHighResIcons(pages.getNextPage()); + } } public void beginExternalDrag(ShortcutInfo item) { diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java index a3e82959a..dbfedaafa 100644 --- a/src/com/android/launcher3/FolderIcon.java +++ b/src/com/android/launcher3/FolderIcon.java @@ -58,7 +58,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private CheckLongPressHelper mLongPressHelper; // The number of icons to display in the - private static final int NUM_ITEMS_IN_PREVIEW = 3; + public static final int NUM_ITEMS_IN_PREVIEW = 3; private static final int CONSUMPTION_ANIMATION_DURATION = 100; private static final int DROP_IN_ANIMATION_DURATION = 400; private static final int INITIAL_ITEM_ANIMATION_DURATION = 350; diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java index 529064444..21158b450 100644 --- a/src/com/android/launcher3/FolderPagedView.java +++ b/src/com/android/launcher3/FolderPagedView.java @@ -641,6 +641,28 @@ public class FolderPagedView extends PagedView implements Folder.FolderContent { return p == getNextPage(); } + @Override + protected void onPageBeginMoving() { + super.onPageBeginMoving(); + getVisiblePages(sTempPosArray); + for (int i = sTempPosArray[0]; i <= sTempPosArray[1]; i++) { + verifyVisibleHighResIcons(i); + } + } + + /** + * Ensures that all the icons on the given page are of high-res + */ + public void verifyVisibleHighResIcons(int pageNo) { + CellLayout page = getPageAt(pageNo); + if (page != null) { + ShortcutAndWidgetContainer parent = page.getShortcutsAndWidgets(); + for (int i = parent.getChildCount() - 1; i >= 0; i--) { + ((BubbleTextView) parent.getChildAt(i)).verifyHighRes(); + } + } + } + @Override public void realTimeReorder(int empty, int target) { completePendingPageChanges(); diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index 43f838e7f..57d23a7bb 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -31,8 +31,10 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.drawable.Drawable; +import android.os.Handler; import android.text.TextUtils; import android.util.Log; @@ -62,10 +64,13 @@ public class IconCache { private static final boolean DEBUG = false; + private static final int LOW_RES_SCALE_FACTOR = 8; + private static class CacheEntry { public Bitmap icon; public CharSequence title; public CharSequence contentDescription; + public boolean isLowResIcon; } private final HashMap mDefaultIcons = @@ -79,6 +84,8 @@ public class IconCache { private final int mIconDpi; private final IconDB mIconDb; + private final Handler mWorkerHandler; + public IconCache(Context context) { ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); @@ -89,6 +96,8 @@ public class IconCache { mLauncherApps = LauncherAppsCompat.getInstance(mContext); mIconDpi = activityManager.getLauncherLargeIconDensity(); mIconDb = new IconDB(context); + + mWorkerHandler = new Handler(LauncherModel.sWorkerThread.getLooper()); } private Drawable getFullResDefaultActivityIcon() { @@ -306,10 +315,7 @@ public class IconCache { entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser()); mCache.put(new ComponentKey(app.getComponentName(), app.getUser()), entry); - ContentValues values = new ContentValues(); - values.put(IconDB.COLUMN_ICON, ItemInfo.flattenBitmap(entry.icon)); - values.put(IconDB.COLUMN_LABEL, entry.title.toString()); - return values; + return mIconDb.newContentValues(entry.icon, entry.title.toString()); } @@ -335,16 +341,52 @@ public class IconCache { } /** - * Fill in "application" with the icon and label for "info." + * Fetches high-res icon for the provided ItemInfo and updates the caller when done. + * @return a request ID that can be used to cancel the request. */ - public synchronized void getTitleAndIcon(AppInfo application, LauncherActivityInfoCompat info) { - CacheEntry entry = cacheLocked(application.componentName, info, info.getUser(), false); + public IconLoadRequest updateIconInBackground(final BubbleTextView caller, final ItemInfo info) { + Runnable request = new Runnable() { + + @Override + public void run() { + if (info instanceof AppInfo) { + getTitleAndIcon((AppInfo) info, null, false); + } else if (info instanceof ShortcutInfo) { + ShortcutInfo st = (ShortcutInfo) info; + getTitleAndIcon(st, + st.promisedIntent != null ? st.promisedIntent : st.intent, + st.user, false); + } + caller.post(new Runnable() { + + @Override + public void run() { + caller.reapplyItemInfo(info); + } + }); + } + }; + mWorkerHandler.post(request); + return new IconLoadRequest(request, mWorkerHandler); + } + /** + * Fill in "application" with the icon and label for "info." + */ + public synchronized void getTitleAndIcon(AppInfo application, + LauncherActivityInfoCompat info, boolean useLowResIcon) { + CacheEntry entry = cacheLocked(application.componentName, info, + info == null ? application.user : info.getUser(), + false, useLowResIcon); application.title = entry.title; application.iconBitmap = entry.icon; application.contentDescription = entry.contentDescription; + application.usingLowResIcon = entry.isLowResIcon; } + /** + * Returns a high res icon for the given intent and user + */ public synchronized Bitmap getIcon(Intent intent, UserHandleCompat user) { ComponentName component = intent.getComponent(); // null info means not installed, but if we have a component from the intent then @@ -354,7 +396,7 @@ public class IconCache { } LauncherActivityInfoCompat launcherActInfo = mLauncherApps.resolveActivity(intent, user); - CacheEntry entry = cacheLocked(component, launcherActInfo, user, true); + CacheEntry entry = cacheLocked(component, launcherActInfo, user, true, true); return entry.icon; } @@ -363,7 +405,7 @@ public class IconCache { * corresponding activity is not found, it reverts to the package icon. */ public synchronized void getTitleAndIcon(ShortcutInfo shortcutInfo, Intent intent, - UserHandleCompat user) { + UserHandleCompat user, boolean useLowResIcon) { ComponentName component = intent.getComponent(); // null info means not installed, but if we have a component from the intent then // we should still look in the cache for restored app icons. @@ -371,9 +413,10 @@ public class IconCache { shortcutInfo.setIcon(getDefaultIcon(user)); shortcutInfo.title = ""; shortcutInfo.usingFallbackIcon = true; + shortcutInfo.usingLowResIcon = false; } else { LauncherActivityInfoCompat info = mLauncherApps.resolveActivity(intent, user); - getTitleAndIcon(shortcutInfo, component, info, user, true); + getTitleAndIcon(shortcutInfo, component, info, user, true, useLowResIcon); } } @@ -382,11 +425,12 @@ public class IconCache { */ public synchronized void getTitleAndIcon( ShortcutInfo shortcutInfo, ComponentName component, LauncherActivityInfoCompat info, - UserHandleCompat user, boolean usePkgIcon) { - CacheEntry entry = cacheLocked(component, info, user, usePkgIcon); + UserHandleCompat user, boolean usePkgIcon, boolean useLowResIcon) { + CacheEntry entry = cacheLocked(component, info, user, usePkgIcon, useLowResIcon); shortcutInfo.setIcon(entry.icon); shortcutInfo.title = entry.title; shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user); + shortcutInfo.usingLowResIcon = entry.isLowResIcon; } public synchronized Bitmap getDefaultIcon(UserHandleCompat user) { @@ -405,15 +449,15 @@ public class IconCache { * This method is not thread safe, it must be called from a synchronized method. */ private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info, - UserHandleCompat user, boolean usePackageIcon) { + UserHandleCompat user, boolean usePackageIcon, boolean useLowResIcon) { ComponentKey cacheKey = new ComponentKey(componentName, user); CacheEntry entry = mCache.get(cacheKey); - if (entry == null) { + if (entry == null || (entry.isLowResIcon && !useLowResIcon)) { entry = new CacheEntry(); mCache.put(cacheKey, entry); // Check the DB first. - if (!getEntryFromDB(componentName, user, entry)) { + if (!getEntryFromDB(componentName, user, entry, useLowResIcon)) { if (info != null) { entry.icon = Utilities.createIconBitmap(info.getBadgedIcon(mIconDpi), mContext); } else { @@ -509,25 +553,26 @@ public class IconCache { // pass } - ContentValues values = new ContentValues(); + ContentValues values = mIconDb.newContentValues(icon, label); values.put(IconDB.COLUMN_COMPONENT, componentName.flattenToString()); values.put(IconDB.COLUMN_USER, userSerial); - values.put(IconDB.COLUMN_ICON, ItemInfo.flattenBitmap(icon)); - values.put(IconDB.COLUMN_LABEL, label); mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE); } - private boolean getEntryFromDB(ComponentName component, UserHandleCompat user, CacheEntry entry) { + private boolean getEntryFromDB(ComponentName component, UserHandleCompat user, + CacheEntry entry, boolean lowRes) { Cursor c = mIconDb.getReadableDatabase().query(IconDB.TABLE_NAME, - new String[] {IconDB.COLUMN_ICON, IconDB.COLUMN_LABEL}, + new String[] {lowRes ? IconDB.COLUMN_ICON_LOW_RES : IconDB.COLUMN_ICON, + IconDB.COLUMN_LABEL}, IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?", new String[] {component.flattenToString(), Long.toString(mUserManager.getSerialNumberForUser(user))}, null, null, null); try { if (c.moveToNext()) { - entry.icon = Utilities.createIconBitmap(c, 0, mContext); + entry.icon = loadIconNoResize(c, 0); + entry.isLowResIcon = lowRes; entry.title = c.getString(1); if (entry.title == null) { entry.title = ""; @@ -543,8 +588,22 @@ public class IconCache { return false; } + public static class IconLoadRequest { + private final Runnable mRunnable; + private final Handler mHandler; + + IconLoadRequest(Runnable runnable, Handler handler) { + mRunnable = runnable; + mHandler = handler; + } + + public void cancel() { + mHandler.removeCallbacks(mRunnable); + } + } + private static final class IconDB extends SQLiteOpenHelper { - private final static int DB_VERSION = 1; + private final static int DB_VERSION = 2; private final static String TABLE_NAME = "icons"; private final static String COLUMN_ROWID = "rowid"; @@ -553,6 +612,7 @@ public class IconCache { private final static String COLUMN_LAST_UPDATED = "lastUpdated"; private final static String COLUMN_VERSION = "version"; private final static String COLUMN_ICON = "icon"; + private final static String COLUMN_ICON_LOW_RES = "icon_low_res"; private final static String COLUMN_LABEL = "label"; public IconDB(Context context) { @@ -567,6 +627,7 @@ public class IconCache { COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " + COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " + COLUMN_ICON + " BLOB, " + + COLUMN_ICON_LOW_RES + " BLOB, " + COLUMN_LABEL + " TEXT, " + "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " + ");"); @@ -590,5 +651,25 @@ public class IconCache { db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); onCreate(db); } + + 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( + Bitmap.createScaledBitmap(icon, + icon.getWidth() / LOW_RES_SCALE_FACTOR, + icon.getHeight() / LOW_RES_SCALE_FACTOR, true))); + values.put(IconDB.COLUMN_LABEL, label); + return values; + } + } + + private static Bitmap loadIconNoResize(Cursor c, int iconIndex) { + byte[] data = c.getBlob(iconIndex); + try { + return BitmapFactory.decodeByteArray(data, 0, data.length); + } catch (Exception e) { + return null; + } } } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index d80debb07..1e16bafe8 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -117,7 +117,7 @@ public class LauncherModel extends BroadcastReceiver private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings"; - private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); + static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); static { sWorkerThread.start(); } @@ -2018,10 +2018,14 @@ public class LauncherModel extends BroadcastReceiver continue; } + container = c.getInt(containerIndex); + boolean useLowResIcon = container >= 0 && + c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW; + if (itemReplaced) { if (user.equals(UserHandleCompat.myUserHandle())) { info = getAppShortcutInfo(manager, intent, user, context, null, - iconIndex, titleIndex, false); + iconIndex, titleIndex, false, useLowResIcon); } else { // Don't replace items for other profiles. itemsToRemove.add(id); @@ -2032,7 +2036,8 @@ public class LauncherModel extends BroadcastReceiver Launcher.addDumpLog(TAG, "constructing info for partially restored package", true); - info = getRestoredItemInfo(c, titleIndex, intent, promiseType); + info = getRestoredItemInfo(c, titleIndex, intent, + promiseType, useLowResIcon); intent = getRestoredItemIntent(c, context, intent); } else { // Don't restore items for other profiles. @@ -2042,7 +2047,7 @@ public class LauncherModel extends BroadcastReceiver } else if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { info = getAppShortcutInfo(manager, intent, user, context, c, - iconIndex, titleIndex, allowMissingTarget); + iconIndex, titleIndex, allowMissingTarget, useLowResIcon); } else { info = getShortcutInfo(c, context, iconTypeIndex, iconPackageIndex, iconResourceIndex, iconIndex, @@ -2064,7 +2069,6 @@ public class LauncherModel extends BroadcastReceiver if (info != null) { info.id = id; info.intent = intent; - container = c.getInt(containerIndex); info.container = container; info.screenId = c.getInt(screenIndex); info.cellX = c.getInt(cellXIndex); @@ -3352,10 +3356,10 @@ public class LauncherModel extends BroadcastReceiver * to a package that is not yet installed on the system. */ public ShortcutInfo getRestoredItemInfo(Cursor cursor, int titleIndex, Intent intent, - int promiseType) { + int promiseType, boolean useLowResIcon) { final ShortcutInfo info = new ShortcutInfo(); info.user = UserHandleCompat.myUserHandle(); - mIconCache.getTitleAndIcon(info, intent, info.user); + mIconCache.getTitleAndIcon(info, intent, info.user, useLowResIcon); if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) { String title = (cursor != null) ? cursor.getString(titleIndex) : null; @@ -3404,7 +3408,7 @@ public class LauncherModel extends BroadcastReceiver */ public ShortcutInfo getAppShortcutInfo(PackageManager manager, Intent intent, UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex, - boolean allowMissingTarget) { + boolean allowMissingTarget, boolean useLowResIcon) { if (user == null) { Log.d(TAG, "Null user found in getShortcutInfo"); return null; @@ -3426,7 +3430,7 @@ public class LauncherModel extends BroadcastReceiver } final ShortcutInfo info = new ShortcutInfo(); - mIconCache.getTitleAndIcon(info, componentName, lai, user, false); + mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon); if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) { Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context); info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon); diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index 08ffaa299..9f7da6c3d 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -83,6 +83,11 @@ public class ShortcutInfo extends ItemInfo { */ boolean usingFallbackIcon; + /** + * Indicates whether we're using a low res icon + */ + boolean usingLowResIcon; + /** * If isShortcut=true and customIcon=false, this contains a reference to the * shortcut icon as an application's resource. @@ -192,7 +197,8 @@ public class ShortcutInfo extends ItemInfo { public void updateIcon(IconCache iconCache) { if (itemType == Favorites.ITEM_TYPE_APPLICATION) { - iconCache.getTitleAndIcon(this, promisedIntent != null ? promisedIntent : intent, user); + iconCache.getTitleAndIcon(this, promisedIntent != null ? promisedIntent : intent, user, + shouldUseLowResIcon()); } } @@ -264,5 +270,9 @@ public class ShortcutInfo extends ItemInfo { mInstallProgress = progress; status |= FLAG_INSTALL_SESSION_ACTIVE; } + + public boolean shouldUseLowResIcon() { + return usingLowResIcon && container >= 0 && rank >= FolderIcon.NUM_ITEMS_IN_PREVIEW; + } } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index a59e25e08..7ebdf3ace 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -4817,7 +4817,8 @@ public class Workspace extends SmoothPagedView if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) { // For auto install apps update the icon as well as label. mIconCache.getTitleAndIcon(shortcutInfo, - shortcutInfo.promisedIntent, user); + shortcutInfo.promisedIntent, user, + shortcutInfo.shouldUseLowResIcon()); } else { // Only update the icon for restored apps. shortcutInfo.updateIcon(mIconCache); -- cgit v1.2.3