Merge "Support for running tests out of Launcher process" into ub-launcher3-master
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index 88f2315..54269f09 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -220,7 +220,7 @@
}
@Override
- public boolean onDrag(float displacement, float velocity) {
+ public boolean onDrag(float displacement) {
float totalDisplacement = displacement + mDisplacementShift;
boolean isGoingUp =
totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 1a9915c..d9626c4 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -280,7 +280,6 @@
factory.createActivityController(RECENTS_LAUNCH_DURATION, INTERACTION_NORMAL);
mActivity = activity;
mRecentsView = mActivity.getOverviewPanel();
- mRecentsView.setRunningTaskIconScaledDown(true);
if (!mUserEventLogged) {
activity.getUserEventDispatcher().logActionCommand(Action.Command.RECENTS_BUTTON,
mHelper.getContainerType(), ContainerType.TASKSWITCHER);
@@ -298,6 +297,9 @@
if (mListener != null) {
mListener.unregister();
}
+ if (mRecentsView != null) {
+ mRecentsView.setRunningTaskIconScaledDown(true);
+ }
AnimatorSet anim = new AnimatorSet();
anim.addListener(new AnimationSuccessListener() {
@Override
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
index c3ce439..8aaa40c 100644
--- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -239,7 +239,7 @@
updateStackBoundsToMultiWindowTaskSize(activity);
} else {
mSourceStackBounds.set(mHomeStackBounds);
- mSourceInsets.set(activity.getDeviceProfile().getInsets());
+ mSourceInsets.set(ttv.getInsets());
}
TransformedRect targetRect = new TransformedRect();
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 253e3af..ed0f09d 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -114,8 +114,7 @@
<string name="action_move_here" msgid="2170188780612570250">"இங்கு நகர்த்து"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"முகப்புத் திரையில் சேர்க்கப்பட்டது"</string>
<string name="item_removed" msgid="851119963877842327">"அகற்றப்பட்டது"</string>
- <!-- no translation found for undo (4151576204245173321) -->
- <skip />
+ <string name="undo" msgid="4151576204245173321">"செயல்தவிர்"</string>
<string name="action_move" msgid="4339390619886385032">"நகர்த்து"</string>
<string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> வரிசை, <xliff:g id="NUMBER_1">%2$s</xliff:g> நெடுவரிசைக்கு நகர்த்து"</string>
<string name="move_to_position" msgid="6750008980455459790">"நிலை <xliff:g id="NUMBER">%1$s</xliff:g>க்கு நகர்த்து"</string>
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 3d60605..733f295 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -26,6 +26,7 @@
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.ItemInfoMatcher;
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 5d40143..8513d63 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -40,8 +40,8 @@
import android.view.ViewDebug;
import android.widget.TextView;
-import com.android.launcher3.IconCache.IconLoadRequest;
-import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.icons.IconCache.IconLoadRequest;
+import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.Launcher.OnResumeCallback;
import com.android.launcher3.badge.BadgeInfo;
import com.android.launcher3.badge.BadgeRenderer;
@@ -272,8 +272,8 @@
/**
* Overrides the default long press timeout.
*/
- public void setLongPressTimeout(int longPressTimeout) {
- mLongPressHelper.setLongPressTimeout(longPressTimeout);
+ public void setLongPressTimeoutFactor(float longPressTimeoutFactor) {
+ mLongPressHelper.setLongPressTimeoutFactor(longPressTimeoutFactor);
}
@Override
diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java
index dde733c..639c173 100644
--- a/src/com/android/launcher3/CheckLongPressHelper.java
+++ b/src/com/android/launcher3/CheckLongPressHelper.java
@@ -17,17 +17,18 @@
package com.android.launcher3;
import android.view.View;
+import android.view.ViewConfiguration;
import com.android.launcher3.util.Thunk;
public class CheckLongPressHelper {
- public static final int DEFAULT_LONG_PRESS_TIMEOUT = 300;
+ public static final float DEFAULT_LONG_PRESS_TIMEOUT_FACTOR = 0.75f;
@Thunk View mView;
@Thunk View.OnLongClickListener mListener;
@Thunk boolean mHasPerformedLongPress;
- private int mLongPressTimeout = DEFAULT_LONG_PRESS_TIMEOUT;
+ private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR;
private CheckForLongPress mPendingCheckForLongPress;
class CheckForLongPress implements Runnable {
@@ -60,8 +61,8 @@
/**
* Overrides the default long press timeout.
*/
- public void setLongPressTimeout(int longPressTimeout) {
- mLongPressTimeout = longPressTimeout;
+ public void setLongPressTimeoutFactor(float longPressTimeoutFactor) {
+ mLongPressTimeoutFactor = longPressTimeoutFactor;
}
public void postCheckForLongPress() {
@@ -70,7 +71,8 @@
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
- mView.postDelayed(mPendingCheckForLongPress, mLongPressTimeout);
+ mView.postDelayed(mPendingCheckForLongPress,
+ (long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor));
}
public void cancelLongPress() {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 9839c12..b429018 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -373,7 +373,7 @@
updateFolderCellSize(1f, dm, res);
// Don't let the folder get too close to the edges of the screen.
- int folderMargin = edgeMarginPx;
+ int folderMargin = edgeMarginPx * 2;
Point totalWorkspacePadding = getTotalWorkspacePadding();
// Check if the icons fit within the available height.
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
deleted file mode 100644
index 61fb3e3..0000000
--- a/src/com/android/launcher3/IconCache.java
+++ /dev/null
@@ -1,865 +0,0 @@
-/*
- * Copyright (C) 2008 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 static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON;
-
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.Drawable;
-import android.os.Build.VERSION;
-import android.os.Handler;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.graphics.BitmapInfo;
-import com.android.launcher3.graphics.BitmapRenderer;
-import com.android.launcher3.graphics.LauncherIcons;
-import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.InstantAppResolver;
-import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.Provider;
-import com.android.launcher3.util.SQLiteCacheHelper;
-import com.android.launcher3.util.Thunk;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.Stack;
-
-import androidx.annotation.NonNull;
-import androidx.core.graphics.ColorUtils;
-
-/**
- * Cache of application icons. Icons can be made from any thread.
- */
-public class IconCache {
-
- private static final String TAG = "Launcher.IconCache";
-
- private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
-
- // Empty class name is used for storing package default entry.
- public static final String EMPTY_CLASS_NAME = ".";
-
- private static final boolean DEBUG = false;
- private static final boolean DEBUG_IGNORE_CACHE = false;
-
- @Thunk static final Object ICON_UPDATE_TOKEN = new Object();
-
- public static class CacheEntry extends BitmapInfo {
- public CharSequence title = "";
- public CharSequence contentDescription = "";
-
- public boolean isLowRes() {
- return LOW_RES_ICON == icon;
- }
- }
-
- private final HashMap<UserHandle, BitmapInfo> mDefaultIcons = new HashMap<>();
- @Thunk final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
-
- private final Context mContext;
- private final PackageManager mPackageManager;
- private final IconProvider mIconProvider;
- @Thunk final UserManagerCompat mUserManager;
- private final LauncherAppsCompat mLauncherApps;
- private final HashMap<ComponentKey, CacheEntry> mCache =
- new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);
- private final InstantAppResolver mInstantAppResolver;
- private final int mIconDpi;
- @Thunk final IconDB mIconDb;
-
- @Thunk final Handler mWorkerHandler;
-
- private final BitmapFactory.Options mDecodeOptions;
-
- private int mPendingIconRequestCount = 0;
-
- public IconCache(Context context, InvariantDeviceProfile inv) {
- mContext = context;
- mPackageManager = context.getPackageManager();
- mUserManager = UserManagerCompat.getInstance(mContext);
- mLauncherApps = LauncherAppsCompat.getInstance(mContext);
- mInstantAppResolver = InstantAppResolver.newInstance(mContext);
- mIconDpi = inv.fillResIconDpi;
- mIconDb = new IconDB(context, inv.iconBitmapSize);
-
- mIconProvider = IconProvider.newInstance(context);
- mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
-
- if (BitmapRenderer.USE_HARDWARE_BITMAP) {
- mDecodeOptions = new BitmapFactory.Options();
- mDecodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
- } else {
- mDecodeOptions = null;
- }
- }
-
- private Drawable getFullResDefaultActivityIcon() {
- return getFullResIcon(Resources.getSystem(), Utilities.ATLEAST_OREO ?
- android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon);
- }
-
- private Drawable getFullResIcon(Resources resources, int iconId) {
- Drawable d;
- try {
- d = resources.getDrawableForDensity(iconId, mIconDpi);
- } catch (Resources.NotFoundException e) {
- d = null;
- }
-
- return (d != null) ? d : getFullResDefaultActivityIcon();
- }
-
- public Drawable getFullResIcon(String packageName, int iconId) {
- Resources resources;
- try {
- resources = mPackageManager.getResourcesForApplication(packageName);
- } catch (PackageManager.NameNotFoundException e) {
- resources = null;
- }
- if (resources != null) {
- if (iconId != 0) {
- return getFullResIcon(resources, iconId);
- }
- }
- return getFullResDefaultActivityIcon();
- }
-
- public Drawable getFullResIcon(ActivityInfo info) {
- Resources resources;
- try {
- resources = mPackageManager.getResourcesForApplication(
- info.applicationInfo);
- } catch (PackageManager.NameNotFoundException e) {
- resources = null;
- }
- if (resources != null) {
- int iconId = info.getIconResource();
- if (iconId != 0) {
- return getFullResIcon(resources, iconId);
- }
- }
-
- return getFullResDefaultActivityIcon();
- }
-
- public Drawable getFullResIcon(LauncherActivityInfo info) {
- return getFullResIcon(info, true);
- }
-
- public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) {
- return mIconProvider.getIcon(info, mIconDpi, flattenDrawable);
- }
-
- protected BitmapInfo makeDefaultIcon(UserHandle user) {
- try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
- return li.createBadgedIconBitmap(
- getFullResDefaultActivityIcon(), user, VERSION.SDK_INT);
- }
- }
-
- /**
- * Remove any records for the supplied ComponentName.
- */
- public synchronized void remove(ComponentName componentName, UserHandle user) {
- mCache.remove(new ComponentKey(componentName, user));
- }
-
- /**
- * Remove any records for the supplied package name from memory.
- */
- private void removeFromMemCacheLocked(String packageName, UserHandle user) {
- HashSet<ComponentKey> forDeletion = new HashSet<>();
- for (ComponentKey key: mCache.keySet()) {
- if (key.componentName.getPackageName().equals(packageName)
- && key.user.equals(user)) {
- forDeletion.add(key);
- }
- }
- for (ComponentKey condemned: forDeletion) {
- mCache.remove(condemned);
- }
- }
-
- /**
- * Updates the entries related to the given package in memory and persistent DB.
- */
- public synchronized void updateIconsForPkg(String packageName, UserHandle user) {
- removeIconsForPkg(packageName, user);
- try {
- PackageInfo info = mPackageManager.getPackageInfo(packageName,
- PackageManager.GET_UNINSTALLED_PACKAGES);
- long userSerial = mUserManager.getSerialNumberForUser(user);
- for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
- addIconToDBAndMemCache(app, info, userSerial, false /*replace existing*/);
- }
- } catch (NameNotFoundException e) {
- Log.d(TAG, "Package not found", e);
- }
- }
-
- /**
- * Removes the entries related to the given package in memory and persistent DB.
- */
- public synchronized void removeIconsForPkg(String packageName, UserHandle user) {
- removeFromMemCacheLocked(packageName, user);
- long userSerial = mUserManager.getSerialNumberForUser(user);
- mIconDb.delete(
- IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
- new String[]{packageName + "/%", Long.toString(userSerial)});
- }
-
- public void updateDbIcons(Set<String> ignorePackagesForMainUser) {
- // Remove all active icon update tasks.
- mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
-
- mIconProvider.updateSystemStateString(mContext);
- for (UserHandle user : mUserManager.getUserProfiles()) {
- // Query for the set of apps
- final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
- // Fail if we don't have any apps
- // TODO: Fix this. Only fail for the current user.
- if (apps == null || apps.isEmpty()) {
- return;
- }
-
- // Update icon cache. This happens in segments and {@link #onPackageIconsUpdated}
- // is called by the icon cache when the job is complete.
- updateDBIcons(user, apps, Process.myUserHandle().equals(user)
- ? ignorePackagesForMainUser : Collections.<String>emptySet());
- }
- }
-
- /**
- * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
- * the DB and are updated.
- * @return The set of packages for which icons have updated.
- */
- private void updateDBIcons(UserHandle user, List<LauncherActivityInfo> apps,
- Set<String> ignorePackages) {
- long userSerial = mUserManager.getSerialNumberForUser(user);
- PackageManager pm = mContext.getPackageManager();
- HashMap<String, PackageInfo> pkgInfoMap = new HashMap<>();
- for (PackageInfo info : pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
- pkgInfoMap.put(info.packageName, info);
- }
-
- HashMap<ComponentName, LauncherActivityInfo> componentMap = new HashMap<>();
- for (LauncherActivityInfo app : apps) {
- componentMap.put(app.getComponentName(), app);
- }
-
- HashSet<Integer> itemsToRemove = new HashSet<>();
- Stack<LauncherActivityInfo> appsToUpdate = new Stack<>();
-
- Cursor c = null;
- try {
- c = mIconDb.query(
- new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
- IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
- IconDB.COLUMN_SYSTEM_STATE},
- IconDB.COLUMN_USER + " = ? ",
- new String[]{Long.toString(userSerial)});
-
- final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
- final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
- final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
- final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
- final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
-
- while (c.moveToNext()) {
- String cn = c.getString(indexComponent);
- ComponentName component = ComponentName.unflattenFromString(cn);
- PackageInfo info = pkgInfoMap.get(component.getPackageName());
- if (info == null) {
- if (!ignorePackages.contains(component.getPackageName())) {
- remove(component, user);
- itemsToRemove.add(c.getInt(rowIndex));
- }
- continue;
- }
- if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
- // Application is not present
- continue;
- }
-
- long updateTime = c.getLong(indexLastUpdate);
- int version = c.getInt(indexVersion);
- LauncherActivityInfo app = componentMap.remove(component);
- if (version == info.versionCode && updateTime == info.lastUpdateTime &&
- TextUtils.equals(c.getString(systemStateIndex),
- mIconProvider.getIconSystemState(info.packageName))) {
- continue;
- }
- if (app == null) {
- remove(component, user);
- itemsToRemove.add(c.getInt(rowIndex));
- } else {
- appsToUpdate.add(app);
- }
- }
- } catch (SQLiteException e) {
- Log.d(TAG, "Error reading icon cache", e);
- // Continue updating whatever we have read so far
- } finally {
- if (c != null) {
- c.close();
- }
- }
- if (!itemsToRemove.isEmpty()) {
- mIconDb.delete(
- Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, itemsToRemove), null);
- }
-
- // Insert remaining apps.
- if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
- Stack<LauncherActivityInfo> appsToAdd = new Stack<>();
- appsToAdd.addAll(componentMap.values());
- new SerializedIconUpdateTask(userSerial, pkgInfoMap,
- appsToAdd, appsToUpdate).scheduleNext();
- }
- }
-
- /**
- * Adds an entry into the DB and the in-memory cache.
- * @param replaceExisting if true, it will recreate the bitmap even if it already exists in
- * the memory. This is useful then the previous bitmap was created using
- * old data.
- */
- @Thunk synchronized void addIconToDBAndMemCache(LauncherActivityInfo app,
- PackageInfo info, long userSerial, boolean replaceExisting) {
- final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
- CacheEntry entry = null;
- if (!replaceExisting) {
- entry = mCache.get(key);
- // We can't reuse the entry if the high-res icon is not present.
- if (entry == null || entry.icon == null || entry.isLowRes()) {
- entry = null;
- }
- }
- if (entry == null) {
- entry = new CacheEntry();
- LauncherIcons li = LauncherIcons.obtain(mContext);
- li.createBadgedIconBitmap(getFullResIcon(app), app.getUser(),
- app.getApplicationInfo().targetSdkVersion).applyTo(entry);
- li.recycle();
- }
- entry.title = app.getLabel();
- entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
- mCache.put(key, entry);
-
- ContentValues values = newContentValues(entry.icon, entry.color,
- entry.title.toString(), app.getApplicationInfo().packageName);
- addIconToDB(values, app.getComponentName(), info, userSerial);
- }
-
- /**
- * Updates {@param values} to contain versioning information and adds it to the DB.
- * @param values {@link ContentValues} containing icon & title
- */
- private void addIconToDB(ContentValues values, ComponentName key,
- PackageInfo info, long userSerial) {
- values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
- values.put(IconDB.COLUMN_USER, userSerial);
- values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
- values.put(IconDB.COLUMN_VERSION, info.versionCode);
- mIconDb.insertOrReplace(values);
- }
-
- /**
- * 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 IconLoadRequest updateIconInBackground(final ItemInfoUpdateReceiver caller,
- final ItemInfoWithIcon info) {
- Preconditions.assertUIThread();
- if (mPendingIconRequestCount <= 0) {
- LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_FOREGROUND);
- }
- mPendingIconRequestCount ++;
-
- IconLoadRequest request = new IconLoadRequest(mWorkerHandler, this::onIconRequestEnd) {
- @Override
- public void run() {
- if (info instanceof AppInfo || info instanceof ShortcutInfo) {
- getTitleAndIcon(info, false);
- } else if (info instanceof PackageItemInfo) {
- getTitleAndIconForApp((PackageItemInfo) info, false);
- }
- mMainThreadExecutor.execute(() -> {
- caller.reapplyItemInfo(info);
- onEnd();
- });
- }
- };
- Utilities.postAsyncCallback(mWorkerHandler, request);
- return request;
- }
-
- private void onIconRequestEnd() {
- mPendingIconRequestCount --;
- if (mPendingIconRequestCount <= 0) {
- LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_BACKGROUND);
- }
- }
-
- /**
- * Updates {@param application} only if a valid entry is found.
- */
- public synchronized void updateTitleAndIcon(AppInfo application) {
- CacheEntry entry = cacheLocked(application.componentName,
- Provider.<LauncherActivityInfo>of(null),
- application.user, false, application.usingLowResIcon());
- if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
- applyCacheEntry(entry, application);
- }
- }
-
- /**
- * Fill in {@param info} with the icon and label for {@param activityInfo}
- */
- public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
- LauncherActivityInfo activityInfo, boolean useLowResIcon) {
- // If we already have activity info, no need to use package icon
- getTitleAndIcon(info, Provider.of(activityInfo), false, useLowResIcon);
- }
-
- /**
- * Fill in {@param info} with the icon and label. If the
- * corresponding activity is not found, it reverts to the package icon.
- */
- public synchronized void getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon) {
- // 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.
- if (info.getTargetComponent() == null) {
- getDefaultIcon(info.user).applyTo(info);
- info.title = "";
- info.contentDescription = "";
- } else {
- Intent intent = info.getIntent();
- getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user),
- true, useLowResIcon);
- }
- }
-
- /**
- * Fill in {@param shortcutInfo} with the icon and label for {@param info}
- */
- private synchronized void getTitleAndIcon(
- @NonNull ItemInfoWithIcon infoInOut,
- @NonNull Provider<LauncherActivityInfo> activityInfoProvider,
- boolean usePkgIcon, boolean useLowResIcon) {
- CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), activityInfoProvider,
- infoInOut.user, usePkgIcon, useLowResIcon);
- applyCacheEntry(entry, infoInOut);
- }
-
- /**
- * Fill in {@param infoInOut} with the corresponding icon and label.
- */
- public synchronized void getTitleAndIconForApp(
- PackageItemInfo infoInOut, boolean useLowResIcon) {
- CacheEntry entry = getEntryForPackageLocked(
- infoInOut.packageName, infoInOut.user, useLowResIcon);
- applyCacheEntry(entry, infoInOut);
- }
-
- private void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
- info.title = Utilities.trim(entry.title);
- info.contentDescription = entry.contentDescription;
- ((entry.icon == null) ? getDefaultIcon(info.user) : entry).applyTo(info);
- }
-
- public synchronized BitmapInfo getDefaultIcon(UserHandle user) {
- if (!mDefaultIcons.containsKey(user)) {
- mDefaultIcons.put(user, makeDefaultIcon(user));
- }
- return mDefaultIcons.get(user);
- }
-
- public boolean isDefaultIcon(Bitmap icon, UserHandle user) {
- return getDefaultIcon(user).icon == icon;
- }
-
- /**
- * Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
- * This method is not thread safe, it must be called from a synchronized method.
- */
- protected CacheEntry cacheLocked(
- @NonNull ComponentName componentName,
- @NonNull Provider<LauncherActivityInfo> infoProvider,
- UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
- Preconditions.assertWorkerThread();
- ComponentKey cacheKey = new ComponentKey(componentName, user);
- CacheEntry entry = mCache.get(cacheKey);
- if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
- entry = new CacheEntry();
- mCache.put(cacheKey, entry);
-
- // Check the DB first.
- LauncherActivityInfo info = null;
- boolean providerFetchedOnce = false;
-
- if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) {
- info = infoProvider.get();
- providerFetchedOnce = true;
-
- if (info != null) {
- LauncherIcons li = LauncherIcons.obtain(mContext);
- li.createBadgedIconBitmap(getFullResIcon(info), info.getUser(),
- info.getApplicationInfo().targetSdkVersion).applyTo(entry);
- li.recycle();
- } else {
- if (usePackageIcon) {
- CacheEntry packageEntry = getEntryForPackageLocked(
- componentName.getPackageName(), user, false);
- if (packageEntry != null) {
- if (DEBUG) Log.d(TAG, "using package default icon for " +
- componentName.toShortString());
- packageEntry.applyTo(entry);
- entry.title = packageEntry.title;
- entry.contentDescription = packageEntry.contentDescription;
- }
- }
- if (entry.icon == null) {
- if (DEBUG) Log.d(TAG, "using default icon for " +
- componentName.toShortString());
- getDefaultIcon(user).applyTo(entry);
- }
- }
- }
-
- if (TextUtils.isEmpty(entry.title)) {
- if (info == null && !providerFetchedOnce) {
- info = infoProvider.get();
- providerFetchedOnce = true;
- }
- if (info != null) {
- entry.title = info.getLabel();
- entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
- }
- }
- }
- return entry;
- }
-
- public synchronized void clear() {
- Preconditions.assertWorkerThread();
- mIconDb.clear();
- }
-
- /**
- * Adds a default package entry in the cache. This entry is not persisted and will be removed
- * when the cache is flushed.
- */
- public synchronized void cachePackageInstallInfo(String packageName, UserHandle user,
- Bitmap icon, CharSequence title) {
- removeFromMemCacheLocked(packageName, user);
-
- ComponentKey cacheKey = getPackageKey(packageName, user);
- CacheEntry entry = mCache.get(cacheKey);
-
- // For icon caching, do not go through DB. Just update the in-memory entry.
- if (entry == null) {
- entry = new CacheEntry();
- }
- if (!TextUtils.isEmpty(title)) {
- entry.title = title;
- }
- if (icon != null) {
- LauncherIcons li = LauncherIcons.obtain(mContext);
- li.createIconBitmap(icon).applyTo(entry);
- li.recycle();
- }
- if (!TextUtils.isEmpty(title) && entry.icon != null) {
- mCache.put(cacheKey, entry);
- }
- }
-
- private static ComponentKey getPackageKey(String packageName, UserHandle user) {
- ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
- return new ComponentKey(cn, user);
- }
-
- /**
- * Gets an entry for the package, which can be used as a fallback entry for various components.
- * This method is not thread safe, it must be called from a synchronized method.
- */
- private CacheEntry getEntryForPackageLocked(String packageName, UserHandle user,
- boolean useLowResIcon) {
- Preconditions.assertWorkerThread();
- ComponentKey cacheKey = getPackageKey(packageName, user);
- CacheEntry entry = mCache.get(cacheKey);
-
- if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
- entry = new CacheEntry();
- boolean entryUpdated = true;
-
- // Check the DB first.
- if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
- try {
- int flags = Process.myUserHandle().equals(user) ? 0 :
- PackageManager.GET_UNINSTALLED_PACKAGES;
- PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
- ApplicationInfo appInfo = info.applicationInfo;
- if (appInfo == null) {
- throw new NameNotFoundException("ApplicationInfo is null");
- }
-
- LauncherIcons li = LauncherIcons.obtain(mContext);
- // Load the full res icon for the application, but if useLowResIcon is set, then
- // only keep the low resolution icon instead of the larger full-sized icon
- BitmapInfo iconInfo = li.createBadgedIconBitmap(
- appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion,
- mInstantAppResolver.isInstantApp(appInfo));
- li.recycle();
-
- entry.title = appInfo.loadLabel(mPackageManager);
- entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
- entry.icon = useLowResIcon ? LOW_RES_ICON : iconInfo.icon;
- entry.color = iconInfo.color;
-
- // Add the icon in the DB here, since these do not get written during
- // package updates.
- ContentValues values = newContentValues(iconInfo.icon, entry.color,
- entry.title.toString(), packageName);
- addIconToDB(values, cacheKey.componentName, info,
- mUserManager.getSerialNumberForUser(user));
-
- } catch (NameNotFoundException e) {
- if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
- entryUpdated = false;
- }
- }
-
- // Only add a filled-out entry to the cache
- if (entryUpdated) {
- mCache.put(cacheKey, entry);
- }
- }
- return entry;
- }
-
- private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
- Cursor c = null;
- try {
- c = mIconDb.query(
- lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES,
- IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
- new String[]{
- cacheKey.componentName.flattenToString(),
- Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))});
- if (c.moveToNext()) {
- // Set the alpha to be 255, so that we never have a wrong color
- entry.color = ColorUtils.setAlphaComponent(c.getInt(0), 255);
- entry.title = c.getString(1);
- if (entry.title == null) {
- entry.title = "";
- entry.contentDescription = "";
- } else {
- entry.contentDescription = mUserManager.getBadgedLabelForUser(
- entry.title, cacheKey.user);
- }
-
- if (lowRes) {
- entry.icon = LOW_RES_ICON;
- } else {
- byte[] data = c.getBlob(2);
- try {
- entry.icon = BitmapFactory.decodeByteArray(data, 0, data.length,
- mDecodeOptions);
- } catch (Exception e) { }
- }
- return true;
- }
- } catch (SQLiteException e) {
- Log.d(TAG, "Error reading icon cache", e);
- } finally {
- if (c != null) {
- c.close();
- }
- }
- return false;
- }
-
- public static abstract class IconLoadRequest implements Runnable {
- private final Handler mHandler;
- private final Runnable mEndRunnable;
-
- private boolean mEnded = false;
-
- IconLoadRequest(Handler handler, Runnable endRunnable) {
- mHandler = handler;
- mEndRunnable = endRunnable;
- }
-
- public void cancel() {
- mHandler.removeCallbacks(this);
- onEnd();
- }
-
- public void onEnd() {
- if (!mEnded) {
- mEnded = true;
- mEndRunnable.run();
- }
- }
- }
-
- /**
- * A runnable that updates invalid icons and adds missing icons in the DB for the provided
- * LauncherActivityInfo list. Items are updated/added one at a time, so that the
- * worker thread doesn't get blocked.
- */
- @Thunk class SerializedIconUpdateTask implements Runnable {
- private final long mUserSerial;
- private final HashMap<String, PackageInfo> mPkgInfoMap;
- private final Stack<LauncherActivityInfo> mAppsToAdd;
- private final Stack<LauncherActivityInfo> mAppsToUpdate;
- private final HashSet<String> mUpdatedPackages = new HashSet<>();
-
- @Thunk SerializedIconUpdateTask(long userSerial, HashMap<String, PackageInfo> pkgInfoMap,
- Stack<LauncherActivityInfo> appsToAdd,
- Stack<LauncherActivityInfo> appsToUpdate) {
- mUserSerial = userSerial;
- mPkgInfoMap = pkgInfoMap;
- mAppsToAdd = appsToAdd;
- mAppsToUpdate = appsToUpdate;
- }
-
- @Override
- public void run() {
- if (!mAppsToUpdate.isEmpty()) {
- LauncherActivityInfo app = mAppsToUpdate.pop();
- String pkg = app.getComponentName().getPackageName();
- PackageInfo info = mPkgInfoMap.get(pkg);
- addIconToDBAndMemCache(app, info, mUserSerial, true /*replace existing*/);
- mUpdatedPackages.add(pkg);
-
- if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
- // No more app to update. Notify model.
- LauncherAppState.getInstance(mContext).getModel().onPackageIconsUpdated(
- mUpdatedPackages, mUserManager.getUserForSerialNumber(mUserSerial));
- }
-
- // Let it run one more time.
- scheduleNext();
- } else if (!mAppsToAdd.isEmpty()) {
- LauncherActivityInfo app = mAppsToAdd.pop();
- PackageInfo info = mPkgInfoMap.get(app.getComponentName().getPackageName());
- // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every
- // app should have package info, this is not guaranteed by the api
- if (info != null) {
- addIconToDBAndMemCache(app, info, mUserSerial, false /*replace existing*/);
- }
-
- if (!mAppsToAdd.isEmpty()) {
- scheduleNext();
- }
- }
- }
-
- public void scheduleNext() {
- mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN, SystemClock.uptimeMillis() + 1);
- }
- }
-
- private static final class IconDB extends SQLiteCacheHelper {
- private final static int RELEASE_VERSION = 25;
-
- private final static String TABLE_NAME = "icons";
- private final static String COLUMN_ROWID = "rowid";
- private final static String COLUMN_COMPONENT = "componentName";
- private final static String COLUMN_USER = "profileId";
- 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_COLOR = "icon_color";
- private final static String COLUMN_LABEL = "label";
- private final static String COLUMN_SYSTEM_STATE = "system_state";
-
- private final static String[] COLUMNS_HIGH_RES = new String[] {
- IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON };
- private final static String[] COLUMNS_LOW_RES = new String[] {
- IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL };
-
- public IconDB(Context context, int iconPixelSize) {
- super(context, LauncherFiles.APP_ICONS_DB,
- (RELEASE_VERSION << 16) + iconPixelSize,
- TABLE_NAME);
- }
-
- @Override
- protected void onCreateTable(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
- COLUMN_COMPONENT + " TEXT NOT NULL, " +
- COLUMN_USER + " INTEGER NOT NULL, " +
- COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " +
- COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " +
- COLUMN_ICON + " BLOB, " +
- COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " +
- COLUMN_LABEL + " TEXT, " +
- COLUMN_SYSTEM_STATE + " TEXT, " +
- "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " +
- ");");
- }
- }
-
- private ContentValues newContentValues(Bitmap icon, int iconColor, String label,
- String packageName) {
- ContentValues values = new ContentValues();
- values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon));
- values.put(IconDB.COLUMN_ICON_COLOR, iconColor);
-
- values.put(IconDB.COLUMN_LABEL, label);
- values.put(IconDB.COLUMN_SYSTEM_STATE, mIconProvider.getIconSystemState(packageName));
-
- return values;
- }
-
- /**
- * Interface for receiving itemInfo with high-res icon.
- */
- public interface ItemInfoUpdateReceiver {
-
- void reapplyItemInfo(ItemInfoWithIcon info);
- }
-}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 151c761..bbe5b78 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -87,6 +87,7 @@
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.FolderIconPreviewVerifier;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.keyboard.CustomActionsPopup;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.logging.FileLog;
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 5159de1..b02182c 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -29,6 +29,7 @@
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 7a90a55..df1c693 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -39,6 +39,7 @@
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.AddWorkspaceItemsTask;
import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index a84bfd5..7717d91 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -25,6 +25,7 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.util.ContentWriter;
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 84bad08..cf497fd 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -33,6 +33,7 @@
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.graphics.ShadowGenerator;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 0f13550..69b4bdb 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -22,7 +22,6 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnFocusChangeListener;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
@@ -252,7 +251,7 @@
R.layout.all_apps_icon, parent, false);
icon.setOnClickListener(ItemClickHandler.INSTANCE);
icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
- icon.setLongPressTimeout(ViewConfiguration.getLongPressTimeout());
+ icon.setLongPressTimeoutFactor(1f);
icon.setOnFocusChangeListener(mIconFocusListener);
// Ensure the all apps icon height matches the workspace icons in portrait mode.
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
index dd17916..fe7b4e5 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
@@ -27,7 +27,7 @@
import android.text.TextUtils;
import android.util.SparseArray;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.config.FeatureFlags;
diff --git a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
index 31c0087..d260e24 100644
--- a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
@@ -32,7 +32,7 @@
import android.util.Log;
import android.widget.Toast;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index 52167bb..bd919bc 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -28,7 +28,7 @@
import android.os.Process;
import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
diff --git a/src/com/android/launcher3/graphics/BitmapInfo.java b/src/com/android/launcher3/graphics/BitmapInfo.java
index 69c0684..74b783d 100644
--- a/src/com/android/launcher3/graphics/BitmapInfo.java
+++ b/src/com/android/launcher3/graphics/BitmapInfo.java
@@ -37,6 +37,10 @@
info.color = color;
}
+ public final boolean isLowRes() {
+ return LOW_RES_ICON == icon;
+ }
+
public static BitmapInfo fromBitmap(Bitmap bitmap) {
return fromBitmap(bitmap, null);
}
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index 8377adf..8cabd2f 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -66,7 +66,7 @@
* Returns a FastBitmapDrawable with the icon.
*/
public FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
- FastBitmapDrawable drawable = info.iconBitmap == LOW_RES_ICON
+ FastBitmapDrawable drawable = info.usingLowResIcon()
? new PlaceHolderIconDrawable(info, getPreloadProgressPath(), context)
: new FastBitmapDrawable(info);
drawable.setIsDisabled(info.isDisabled());
@@ -74,7 +74,7 @@
}
public FastBitmapDrawable newIcon(Context context, BitmapInfo info, ActivityInfo target) {
- return info.icon == LOW_RES_ICON
+ return info.isLowRes()
? new PlaceHolderIconDrawable(info, getPreloadProgressPath(), context)
: new FastBitmapDrawable(info);
}
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 087362c..7db67c9 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -44,7 +44,7 @@
import com.android.launcher3.AppInfo;
import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherAppState;
diff --git a/src/com/android/launcher3/icons/BaseIconCache.java b/src/com/android/launcher3/icons/BaseIconCache.java
new file mode 100644
index 0000000..3c742df
--- /dev/null
+++ b/src/com/android/launcher3/icons/BaseIconCache.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2018 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.icons;
+
+import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON;
+
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+import android.os.Build.VERSION;
+import android.os.Handler;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.IconProvider;
+import com.android.launcher3.ItemInfoWithIcon;
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.graphics.BitmapInfo;
+import com.android.launcher3.graphics.BitmapRenderer;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.Provider;
+import com.android.launcher3.util.SQLiteCacheHelper;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+import androidx.annotation.NonNull;
+import androidx.core.graphics.ColorUtils;
+
+public class BaseIconCache {
+
+ private static final String TAG = "BaseIconCache";
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_IGNORE_CACHE = false;
+
+ private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
+
+ // Empty class name is used for storing package default entry.
+ public static final String EMPTY_CLASS_NAME = ".";
+
+ public static class CacheEntry extends BitmapInfo {
+ public CharSequence title = "";
+ public CharSequence contentDescription = "";
+ }
+
+ private final HashMap<UserHandle, BitmapInfo> mDefaultIcons = new HashMap<>();
+
+ final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
+ final Context mContext;
+ final PackageManager mPackageManager;
+ final IconProvider mIconProvider;
+ final UserManagerCompat mUserManager;
+ final LauncherAppsCompat mLauncherApps;
+
+ private final HashMap<ComponentKey, CacheEntry> mCache =
+ new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);
+ private final InstantAppResolver mInstantAppResolver;
+ final int mIconDpi;
+
+ final IconDB mIconDb;
+ final Handler mWorkerHandler;
+
+ private final BitmapFactory.Options mDecodeOptions;
+
+ public BaseIconCache(Context context, int iconDpi, int iconPixelSize) {
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ mUserManager = UserManagerCompat.getInstance(mContext);
+ mLauncherApps = LauncherAppsCompat.getInstance(mContext);
+ mInstantAppResolver = InstantAppResolver.newInstance(mContext);
+ mIconDpi = iconDpi;
+ mIconDb = new IconDB(context, iconPixelSize);
+
+ mIconProvider = IconProvider.newInstance(context);
+ mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
+
+ if (BitmapRenderer.USE_HARDWARE_BITMAP) {
+ mDecodeOptions = new BitmapFactory.Options();
+ mDecodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
+ } else {
+ mDecodeOptions = null;
+ }
+ }
+
+ private Drawable getFullResDefaultActivityIcon() {
+ return Resources.getSystem().getDrawableForDensity(Utilities.ATLEAST_OREO
+ ? android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon,
+ mIconDpi);
+ }
+
+ private Drawable getFullResIcon(Resources resources, int iconId) {
+ if (resources != null && iconId != 0) {
+ try {
+ return resources.getDrawableForDensity(iconId, mIconDpi);
+ } catch (Resources.NotFoundException e) { }
+ }
+ return getFullResDefaultActivityIcon();
+ }
+
+ public Drawable getFullResIcon(String packageName, int iconId) {
+ try {
+ return getFullResIcon(mPackageManager.getResourcesForApplication(packageName), iconId);
+ } catch (PackageManager.NameNotFoundException e) { }
+ return getFullResDefaultActivityIcon();
+ }
+
+ public Drawable getFullResIcon(ActivityInfo info) {
+ try {
+ return getFullResIcon(mPackageManager.getResourcesForApplication(info.applicationInfo),
+ info.getIconResource());
+ } catch (PackageManager.NameNotFoundException e) { }
+ return getFullResDefaultActivityIcon();
+ }
+
+ public Drawable getFullResIcon(LauncherActivityInfo info) {
+ return getFullResIcon(info, true);
+ }
+
+ public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) {
+ return mIconProvider.getIcon(info, mIconDpi, flattenDrawable);
+ }
+
+ protected BitmapInfo makeDefaultIcon(UserHandle user) {
+ try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+ return li.createBadgedIconBitmap(
+ getFullResDefaultActivityIcon(), user, VERSION.SDK_INT);
+ }
+ }
+
+ /**
+ * Remove any records for the supplied ComponentName.
+ */
+ public synchronized void remove(ComponentName componentName, UserHandle user) {
+ mCache.remove(new ComponentKey(componentName, user));
+ }
+
+ /**
+ * Remove any records for the supplied package name from memory.
+ */
+ private void removeFromMemCacheLocked(String packageName, UserHandle user) {
+ HashSet<ComponentKey> forDeletion = new HashSet<>();
+ for (ComponentKey key: mCache.keySet()) {
+ if (key.componentName.getPackageName().equals(packageName)
+ && key.user.equals(user)) {
+ forDeletion.add(key);
+ }
+ }
+ for (ComponentKey condemned: forDeletion) {
+ mCache.remove(condemned);
+ }
+ }
+
+ /**
+ * Removes the entries related to the given package in memory and persistent DB.
+ */
+ public synchronized void removeIconsForPkg(String packageName, UserHandle user) {
+ removeFromMemCacheLocked(packageName, user);
+ long userSerial = mUserManager.getSerialNumberForUser(user);
+ mIconDb.delete(
+ IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
+ new String[]{packageName + "/%", Long.toString(userSerial)});
+ }
+
+ public IconCacheUpdateHandler getUpdateHandler() {
+ mIconProvider.updateSystemStateString(mContext);
+ return new IconCacheUpdateHandler(this);
+ }
+
+ /**
+ * Adds an entry into the DB and the in-memory cache.
+ * @param replaceExisting if true, it will recreate the bitmap even if it already exists in
+ * the memory. This is useful then the previous bitmap was created using
+ * old data.
+ * package private
+ */
+ synchronized <T> void addIconToDBAndMemCache(T object, CachingLogic<T> cachingLogic,
+ PackageInfo info, long userSerial, boolean replaceExisting) {
+ UserHandle user = cachingLogic.getUser(object);
+ ComponentName componentName = cachingLogic.getComponent(object);
+
+ final ComponentKey key = new ComponentKey(componentName, user);
+ CacheEntry entry = null;
+ if (!replaceExisting) {
+ entry = mCache.get(key);
+ // We can't reuse the entry if the high-res icon is not present.
+ if (entry == null || entry.icon == null || entry.isLowRes()) {
+ entry = null;
+ }
+ }
+ if (entry == null) {
+ entry = new CacheEntry();
+ cachingLogic.loadIcon(mContext, this, object, entry);
+ }
+ entry.title = cachingLogic.getLabel(object);
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+ mCache.put(key, entry);
+
+ ContentValues values = newContentValues(entry.icon, entry.color,
+ entry.title.toString(), componentName.getPackageName());
+ addIconToDB(values, componentName, info, userSerial);
+ }
+
+ /**
+ * Updates {@param values} to contain versioning information and adds it to the DB.
+ * @param values {@link ContentValues} containing icon & title
+ */
+ private void addIconToDB(ContentValues values, ComponentName key,
+ PackageInfo info, long userSerial) {
+ values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
+ values.put(IconDB.COLUMN_USER, userSerial);
+ values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
+ values.put(IconDB.COLUMN_VERSION, info.versionCode);
+ mIconDb.insertOrReplace(values);
+ }
+
+ /**
+ * Fill in {@param infoInOut} with the corresponding icon and label.
+ */
+ public synchronized void getTitleAndIconForApp(
+ PackageItemInfo infoInOut, boolean useLowResIcon) {
+ CacheEntry entry = getEntryForPackageLocked(
+ infoInOut.packageName, infoInOut.user, useLowResIcon);
+ applyCacheEntry(entry, infoInOut);
+ }
+
+ protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
+ info.title = Utilities.trim(entry.title);
+ info.contentDescription = entry.contentDescription;
+ ((entry.icon == null) ? getDefaultIcon(info.user) : entry).applyTo(info);
+ }
+
+ public synchronized BitmapInfo getDefaultIcon(UserHandle user) {
+ if (!mDefaultIcons.containsKey(user)) {
+ mDefaultIcons.put(user, makeDefaultIcon(user));
+ }
+ return mDefaultIcons.get(user);
+ }
+
+ public boolean isDefaultIcon(Bitmap icon, UserHandle user) {
+ return getDefaultIcon(user).icon == icon;
+ }
+
+ /**
+ * Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
+ * This method is not thread safe, it must be called from a synchronized method.
+ */
+ protected <T> CacheEntry cacheLocked(
+ @NonNull ComponentName componentName,
+ @NonNull Provider<T> infoProvider,
+ @NonNull CachingLogic<T> cachingLogic,
+ UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
+ Preconditions.assertWorkerThread();
+ ComponentKey cacheKey = new ComponentKey(componentName, user);
+ CacheEntry entry = mCache.get(cacheKey);
+ if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
+ entry = new CacheEntry();
+ mCache.put(cacheKey, entry);
+
+ // Check the DB first.
+ T object = null;
+ boolean providerFetchedOnce = false;
+
+ if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) {
+ object = infoProvider.get();
+ providerFetchedOnce = true;
+
+ if (object != null) {
+ cachingLogic.loadIcon(mContext, this, object, entry);
+ } else {
+ if (usePackageIcon) {
+ CacheEntry packageEntry = getEntryForPackageLocked(
+ componentName.getPackageName(), user, false);
+ if (packageEntry != null) {
+ if (DEBUG) Log.d(TAG, "using package default icon for " +
+ componentName.toShortString());
+ packageEntry.applyTo(entry);
+ entry.title = packageEntry.title;
+ entry.contentDescription = packageEntry.contentDescription;
+ }
+ }
+ if (entry.icon == null) {
+ if (DEBUG) Log.d(TAG, "using default icon for " +
+ componentName.toShortString());
+ getDefaultIcon(user).applyTo(entry);
+ }
+ }
+ }
+
+ if (TextUtils.isEmpty(entry.title)) {
+ if (object == null && !providerFetchedOnce) {
+ object = infoProvider.get();
+ providerFetchedOnce = true;
+ }
+ if (object != null) {
+ entry.title = cachingLogic.getLabel(object);
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+ }
+ }
+ }
+ return entry;
+ }
+
+ public synchronized void clear() {
+ Preconditions.assertWorkerThread();
+ mIconDb.clear();
+ }
+
+ /**
+ * Adds a default package entry in the cache. This entry is not persisted and will be removed
+ * when the cache is flushed.
+ */
+ public synchronized void cachePackageInstallInfo(String packageName, UserHandle user,
+ Bitmap icon, CharSequence title) {
+ removeFromMemCacheLocked(packageName, user);
+
+ ComponentKey cacheKey = getPackageKey(packageName, user);
+ CacheEntry entry = mCache.get(cacheKey);
+
+ // For icon caching, do not go through DB. Just update the in-memory entry.
+ if (entry == null) {
+ entry = new CacheEntry();
+ }
+ if (!TextUtils.isEmpty(title)) {
+ entry.title = title;
+ }
+ if (icon != null) {
+ LauncherIcons li = LauncherIcons.obtain(mContext);
+ li.createIconBitmap(icon).applyTo(entry);
+ li.recycle();
+ }
+ if (!TextUtils.isEmpty(title) && entry.icon != null) {
+ mCache.put(cacheKey, entry);
+ }
+ }
+
+ private static ComponentKey getPackageKey(String packageName, UserHandle user) {
+ ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
+ return new ComponentKey(cn, user);
+ }
+
+ /**
+ * Gets an entry for the package, which can be used as a fallback entry for various components.
+ * This method is not thread safe, it must be called from a synchronized method.
+ */
+ private CacheEntry getEntryForPackageLocked(String packageName, UserHandle user,
+ boolean useLowResIcon) {
+ Preconditions.assertWorkerThread();
+ ComponentKey cacheKey = getPackageKey(packageName, user);
+ CacheEntry entry = mCache.get(cacheKey);
+
+ if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
+ entry = new CacheEntry();
+ boolean entryUpdated = true;
+
+ // Check the DB first.
+ if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
+ try {
+ int flags = Process.myUserHandle().equals(user) ? 0 :
+ PackageManager.GET_UNINSTALLED_PACKAGES;
+ PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
+ ApplicationInfo appInfo = info.applicationInfo;
+ if (appInfo == null) {
+ throw new NameNotFoundException("ApplicationInfo is null");
+ }
+
+ LauncherIcons li = LauncherIcons.obtain(mContext);
+ // Load the full res icon for the application, but if useLowResIcon is set, then
+ // only keep the low resolution icon instead of the larger full-sized icon
+ BitmapInfo iconInfo = li.createBadgedIconBitmap(
+ appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion,
+ mInstantAppResolver.isInstantApp(appInfo));
+ li.recycle();
+
+ entry.title = appInfo.loadLabel(mPackageManager);
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+ entry.icon = useLowResIcon ? LOW_RES_ICON : iconInfo.icon;
+ entry.color = iconInfo.color;
+
+ // Add the icon in the DB here, since these do not get written during
+ // package updates.
+ ContentValues values = newContentValues(iconInfo.icon, entry.color,
+ entry.title.toString(), packageName);
+ addIconToDB(values, cacheKey.componentName, info,
+ mUserManager.getSerialNumberForUser(user));
+
+ } catch (NameNotFoundException e) {
+ if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
+ entryUpdated = false;
+ }
+ }
+
+ // Only add a filled-out entry to the cache
+ if (entryUpdated) {
+ mCache.put(cacheKey, entry);
+ }
+ }
+ return entry;
+ }
+
+ private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
+ Cursor c = null;
+ try {
+ c = mIconDb.query(
+ lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES,
+ IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
+ new String[]{
+ cacheKey.componentName.flattenToString(),
+ Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))});
+ if (c.moveToNext()) {
+ // Set the alpha to be 255, so that we never have a wrong color
+ entry.color = ColorUtils.setAlphaComponent(c.getInt(0), 255);
+ entry.title = c.getString(1);
+ if (entry.title == null) {
+ entry.title = "";
+ entry.contentDescription = "";
+ } else {
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(
+ entry.title, cacheKey.user);
+ }
+
+ if (lowRes) {
+ entry.icon = LOW_RES_ICON;
+ } else {
+ byte[] data = c.getBlob(2);
+ try {
+ entry.icon = BitmapFactory.decodeByteArray(data, 0, data.length,
+ mDecodeOptions);
+ } catch (Exception e) { }
+ }
+ return true;
+ }
+ } catch (SQLiteException e) {
+ Log.d(TAG, "Error reading icon cache", e);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return false;
+ }
+
+ static final class IconDB extends SQLiteCacheHelper {
+ private final static int RELEASE_VERSION = 25;
+
+ public final static String TABLE_NAME = "icons";
+ public final static String COLUMN_ROWID = "rowid";
+ public final static String COLUMN_COMPONENT = "componentName";
+ public final static String COLUMN_USER = "profileId";
+ public final static String COLUMN_LAST_UPDATED = "lastUpdated";
+ public final static String COLUMN_VERSION = "version";
+ public final static String COLUMN_ICON = "icon";
+ public final static String COLUMN_ICON_COLOR = "icon_color";
+ public final static String COLUMN_LABEL = "label";
+ public final static String COLUMN_SYSTEM_STATE = "system_state";
+
+ public final static String[] COLUMNS_HIGH_RES = new String[] {
+ IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON };
+ public final static String[] COLUMNS_LOW_RES = new String[] {
+ IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL };
+
+ public IconDB(Context context, int iconPixelSize) {
+ super(context, LauncherFiles.APP_ICONS_DB,
+ (RELEASE_VERSION << 16) + iconPixelSize,
+ TABLE_NAME);
+ }
+
+ @Override
+ protected void onCreateTable(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
+ COLUMN_COMPONENT + " TEXT NOT NULL, " +
+ COLUMN_USER + " INTEGER NOT NULL, " +
+ COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " +
+ COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " +
+ COLUMN_ICON + " BLOB, " +
+ COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " +
+ COLUMN_LABEL + " TEXT, " +
+ COLUMN_SYSTEM_STATE + " TEXT, " +
+ "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " +
+ ");");
+ }
+ }
+
+ private ContentValues newContentValues(Bitmap icon, int iconColor, String label,
+ String packageName) {
+ ContentValues values = new ContentValues();
+ values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon));
+ values.put(IconDB.COLUMN_ICON_COLOR, iconColor);
+
+ values.put(IconDB.COLUMN_LABEL, label);
+ values.put(IconDB.COLUMN_SYSTEM_STATE, mIconProvider.getIconSystemState(packageName));
+
+ return values;
+ }
+}
diff --git a/src/com/android/launcher3/icons/CachingLogic.java b/src/com/android/launcher3/icons/CachingLogic.java
new file mode 100644
index 0000000..194fedb
--- /dev/null
+++ b/src/com/android/launcher3/icons/CachingLogic.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2018 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.icons;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
+import android.os.UserHandle;
+
+import com.android.launcher3.graphics.BitmapInfo;
+import com.android.launcher3.graphics.LauncherIcons;
+
+public interface CachingLogic<T> {
+
+ ComponentName getComponent(T object);
+
+ UserHandle getUser(T object);
+
+ CharSequence getLabel(T object);
+
+ void loadIcon(Context context, BaseIconCache cache, T object, BitmapInfo target);
+
+ CachingLogic<LauncherActivityInfo> LAUNCHER_ACTIVITY_INFO =
+ new CachingLogic<LauncherActivityInfo>() {
+
+ @Override
+ public ComponentName getComponent(LauncherActivityInfo object) {
+ return object.getComponentName();
+ }
+
+ @Override
+ public UserHandle getUser(LauncherActivityInfo object) {
+ return object.getUser();
+ }
+
+ @Override
+ public CharSequence getLabel(LauncherActivityInfo object) {
+ return object.getLabel();
+ }
+
+ @Override
+ public void loadIcon(Context context, BaseIconCache cache, LauncherActivityInfo object,
+ BitmapInfo target) {
+ LauncherIcons li = LauncherIcons.obtain(context);
+ li.createBadgedIconBitmap(cache.getFullResIcon(object), object.getUser(),
+ object.getApplicationInfo().targetSdkVersion).applyTo(target);
+ li.recycle();
+ }
+ };
+}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
new file mode 100644
index 0000000..eae6f01
--- /dev/null
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2008 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.icons;
+
+import static com.android.launcher3.icons.CachingLogic.LAUNCHER_ACTIVITY_INFO;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Handler;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfoWithIcon;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.Provider;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Cache of application icons. Icons can be made from any thread.
+ */
+public class IconCache extends BaseIconCache {
+
+ private static final String TAG = "Launcher.IconCache";
+
+ private int mPendingIconRequestCount = 0;
+
+ public IconCache(Context context, InvariantDeviceProfile inv) {
+ super(context, inv.fillResIconDpi, inv.iconBitmapSize);
+ }
+
+ /**
+ * Updates the entries related to the given package in memory and persistent DB.
+ */
+ public synchronized void updateIconsForPkg(String packageName, UserHandle user) {
+ removeIconsForPkg(packageName, user);
+ try {
+ PackageInfo info = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES);
+ long userSerial = mUserManager.getSerialNumberForUser(user);
+ for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
+ addIconToDBAndMemCache(app, LAUNCHER_ACTIVITY_INFO, info, userSerial,
+ false /*replace existing*/);
+ }
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Package not found", e);
+ }
+ }
+
+ /**
+ * 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 IconLoadRequest updateIconInBackground(final ItemInfoUpdateReceiver caller,
+ final ItemInfoWithIcon info) {
+ Preconditions.assertUIThread();
+ if (mPendingIconRequestCount <= 0) {
+ LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_FOREGROUND);
+ }
+ mPendingIconRequestCount ++;
+
+ IconLoadRequest request = new IconLoadRequest(mWorkerHandler, this::onIconRequestEnd) {
+ @Override
+ public void run() {
+ if (info instanceof AppInfo || info instanceof ShortcutInfo) {
+ getTitleAndIcon(info, false);
+ } else if (info instanceof PackageItemInfo) {
+ getTitleAndIconForApp((PackageItemInfo) info, false);
+ }
+ mMainThreadExecutor.execute(() -> {
+ caller.reapplyItemInfo(info);
+ onEnd();
+ });
+ }
+ };
+ Utilities.postAsyncCallback(mWorkerHandler, request);
+ return request;
+ }
+
+ private void onIconRequestEnd() {
+ mPendingIconRequestCount --;
+ if (mPendingIconRequestCount <= 0) {
+ LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ }
+ }
+
+ /**
+ * Updates {@param application} only if a valid entry is found.
+ */
+ public synchronized void updateTitleAndIcon(AppInfo application) {
+ CacheEntry entry = cacheLocked(application.componentName,
+ Provider.of(null), LAUNCHER_ACTIVITY_INFO,
+ application.user, false, application.usingLowResIcon());
+ if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
+ applyCacheEntry(entry, application);
+ }
+ }
+
+ /**
+ * Fill in {@param info} with the icon and label for {@param activityInfo}
+ */
+ public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
+ LauncherActivityInfo activityInfo, boolean useLowResIcon) {
+ // If we already have activity info, no need to use package icon
+ getTitleAndIcon(info, Provider.of(activityInfo), false, useLowResIcon);
+ }
+
+ /**
+ * Fill in {@param info} with the icon and label. If the
+ * corresponding activity is not found, it reverts to the package icon.
+ */
+ public synchronized void getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon) {
+ // 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.
+ if (info.getTargetComponent() == null) {
+ getDefaultIcon(info.user).applyTo(info);
+ info.title = "";
+ info.contentDescription = "";
+ } else {
+ Intent intent = info.getIntent();
+ getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user),
+ true, useLowResIcon);
+ }
+ }
+
+ /**
+ * Fill in {@param shortcutInfo} with the icon and label for {@param info}
+ */
+ private synchronized void getTitleAndIcon(
+ @NonNull ItemInfoWithIcon infoInOut,
+ @NonNull Provider<LauncherActivityInfo> activityInfoProvider,
+ boolean usePkgIcon, boolean useLowResIcon) {
+ CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), activityInfoProvider,
+ LAUNCHER_ACTIVITY_INFO, infoInOut.user, usePkgIcon, useLowResIcon);
+ applyCacheEntry(entry, infoInOut);
+ }
+
+ public static abstract class IconLoadRequest implements Runnable {
+ private final Handler mHandler;
+ private final Runnable mEndRunnable;
+
+ private boolean mEnded = false;
+
+ IconLoadRequest(Handler handler, Runnable endRunnable) {
+ mHandler = handler;
+ mEndRunnable = endRunnable;
+ }
+
+ public void cancel() {
+ mHandler.removeCallbacks(this);
+ onEnd();
+ }
+
+ public void onEnd() {
+ if (!mEnded) {
+ mEnded = true;
+ mEndRunnable.run();
+ }
+ }
+ }
+
+ /**
+ * Interface for receiving itemInfo with high-res icon.
+ */
+ public interface ItemInfoUpdateReceiver {
+
+ void reapplyItemInfo(ItemInfoWithIcon info);
+ }
+}
diff --git a/src/com/android/launcher3/icons/IconCacheUpdateHandler.java b/src/com/android/launcher3/icons/IconCacheUpdateHandler.java
new file mode 100644
index 0000000..e316c53
--- /dev/null
+++ b/src/com/android/launcher3/icons/IconCacheUpdateHandler.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2018 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.icons;
+
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.BaseIconCache.IconDB;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * Utility class to handle updating the Icon cache
+ */
+public class IconCacheUpdateHandler {
+
+ private static final String TAG = "IconCacheUpdateHandler";
+
+ private static final Object ICON_UPDATE_TOKEN = new Object();
+
+ private final HashMap<String, PackageInfo> mPkgInfoMap;
+ private final BaseIconCache mIconCache;
+ private final HashMap<UserHandle, Set<String>> mPackagesToIgnore = new HashMap<>();
+
+ IconCacheUpdateHandler(BaseIconCache cache) {
+ mIconCache = cache;
+
+ mPkgInfoMap = new HashMap<>();
+
+ // Remove all active icon update tasks.
+ mIconCache.mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
+
+ createPackageInfoMap();
+ }
+
+ public void setPackagesToIgnore(UserHandle userHandle, Set<String> packages) {
+ mPackagesToIgnore.put(userHandle, packages);
+ }
+
+ private void createPackageInfoMap() {
+ PackageManager pm = mIconCache.mPackageManager;
+ HashMap<String, PackageInfo> pkgInfoMap = new HashMap<>();
+ for (PackageInfo info : pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
+ pkgInfoMap.put(info.packageName, info);
+ }
+ }
+
+ /**
+ * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
+ * the DB and are updated.
+ * @return The set of packages for which icons have updated.
+ */
+ public <T> void updateIcons(List<T> apps, CachingLogic<T> cachingLogic) {
+ if (apps.isEmpty()) {
+ return;
+ }
+ UserHandle user = cachingLogic.getUser(apps.get(0));
+
+ Set<String> ignorePackages = mPackagesToIgnore.get(user);
+ if (ignorePackages == null) {
+ ignorePackages = Collections.emptySet();
+ }
+
+ long userSerial = mIconCache.mUserManager.getSerialNumberForUser(user);
+ HashMap<ComponentName, T> componentMap = new HashMap<>();
+ for (T app : apps) {
+ componentMap.put(cachingLogic.getComponent(app), app);
+ }
+
+ HashSet<Integer> itemsToRemove = new HashSet<>();
+ Stack<T> appsToUpdate = new Stack<>();
+
+ try (Cursor c = mIconCache.mIconDb.query(
+ new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
+ IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
+ IconDB.COLUMN_SYSTEM_STATE},
+ IconDB.COLUMN_USER + " = ? ",
+ new String[]{Long.toString(userSerial)})) {
+
+ final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
+ final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
+ final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
+ final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
+ final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
+
+ while (c.moveToNext()) {
+ String cn = c.getString(indexComponent);
+ ComponentName component = ComponentName.unflattenFromString(cn);
+ PackageInfo info = mPkgInfoMap.get(component.getPackageName());
+ if (info == null) {
+ if (!ignorePackages.contains(component.getPackageName())) {
+ mIconCache.remove(component, user);
+ itemsToRemove.add(c.getInt(rowIndex));
+ }
+ continue;
+ }
+ if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
+ // Application is not present
+ continue;
+ }
+
+ long updateTime = c.getLong(indexLastUpdate);
+ int version = c.getInt(indexVersion);
+ T app = componentMap.remove(component);
+ if (version == info.versionCode && updateTime == info.lastUpdateTime &&
+ TextUtils.equals(c.getString(systemStateIndex),
+ mIconCache.mIconProvider.getIconSystemState(info.packageName))) {
+ continue;
+ }
+ if (app == null) {
+ mIconCache.remove(component, user);
+ itemsToRemove.add(c.getInt(rowIndex));
+ } else {
+ appsToUpdate.add(app);
+ }
+ }
+ } catch (SQLiteException e) {
+ Log.d(TAG, "Error reading icon cache", e);
+ // Continue updating whatever we have read so far
+ }
+ if (!itemsToRemove.isEmpty()) {
+ mIconCache.mIconDb.delete(
+ Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, itemsToRemove), null);
+ }
+
+ // Insert remaining apps.
+ if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
+ Stack<T> appsToAdd = new Stack<>();
+ appsToAdd.addAll(componentMap.values());
+ new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic)
+ .scheduleNext();
+ }
+ }
+
+
+ /**
+ * A runnable that updates invalid icons and adds missing icons in the DB for the provided
+ * LauncherActivityInfo list. Items are updated/added one at a time, so that the
+ * worker thread doesn't get blocked.
+ */
+ private class SerializedIconUpdateTask<T> implements Runnable {
+ private final long mUserSerial;
+ private final UserHandle mUserHandle;
+ private final Stack<T> mAppsToAdd;
+ private final Stack<T> mAppsToUpdate;
+ private final CachingLogic<T> mCachingLogic;
+ private final HashSet<String> mUpdatedPackages = new HashSet<>();
+
+ SerializedIconUpdateTask(long userSerial, UserHandle userHandle,
+ Stack<T> appsToAdd, Stack<T> appsToUpdate, CachingLogic<T> cachingLogic) {
+ mUserHandle = userHandle;
+ mUserSerial = userSerial;
+ mAppsToAdd = appsToAdd;
+ mAppsToUpdate = appsToUpdate;
+ mCachingLogic = cachingLogic;
+ }
+
+ @Override
+ public void run() {
+ if (!mAppsToUpdate.isEmpty()) {
+ T app = mAppsToUpdate.pop();
+ String pkg = mCachingLogic.getComponent(app).getPackageName();
+ PackageInfo info = mPkgInfoMap.get(pkg);
+ mIconCache.addIconToDBAndMemCache(
+ app, mCachingLogic, info, mUserSerial, true /*replace existing*/);
+ mUpdatedPackages.add(pkg);
+
+ if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
+ // No more app to update. Notify model.
+ LauncherAppState.getInstance(mIconCache.mContext).getModel()
+ .onPackageIconsUpdated(mUpdatedPackages, mUserHandle);
+ }
+
+ // Let it run one more time.
+ scheduleNext();
+ } else if (!mAppsToAdd.isEmpty()) {
+ T app = mAppsToAdd.pop();
+ PackageInfo info = mPkgInfoMap.get(mCachingLogic.getComponent(app).getPackageName());
+ // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every
+ // app should have package info, this is not guaranteed by the api
+ if (info != null) {
+ mIconCache.addIconToDBAndMemCache(app, mCachingLogic, info,
+ mUserSerial, false /*replace existing*/);
+ }
+
+ if (!mAppsToAdd.isEmpty()) {
+ scheduleNext();
+ }
+ }
+ }
+
+ public void scheduleNext() {
+ mIconCache.mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN,
+ SystemClock.uptimeMillis() + 1);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index 54c0542..be83d36 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -20,7 +20,7 @@
import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppInfo;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel.CallbackTask;
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 744e98a..77e9721 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -32,7 +32,7 @@
import android.util.LongSparseArray;
import com.android.launcher3.AppInfo;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 8f8bc09..0105fbb 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+import static com.android.launcher3.icons.CachingLogic.LAUNCHER_ACTIVITY_INFO;
import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems;
import android.appwidget.AppWidgetProviderInfo;
@@ -43,7 +44,8 @@
import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppInfo;
import com.android.launcher3.FolderInfo;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCacheUpdateHandler;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
@@ -182,7 +184,7 @@
// second step
TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");
- loadAllApps();
+ List<List<LauncherActivityInfo>> activityListPerUser = loadAllApps();
TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps");
verifyNotStopped();
@@ -190,7 +192,9 @@
verifyNotStopped();
TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache");
- updateIconCache();
+ IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
+ setIgnorePackages(updateHandler);
+ updateIconCacheForApps(updateHandler, activityListPerUser);
// Take a break
TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle");
@@ -774,7 +778,7 @@
}
}
- private void updateIconCache() {
+ private void setIgnorePackages(IconCacheUpdateHandler updateHandler) {
// Ignore packages which have a promise icon.
HashSet<String> packagesToIgnore = new HashSet<>();
synchronized (mBgDataModel) {
@@ -792,12 +796,20 @@
}
}
}
- mIconCache.updateDbIcons(packagesToIgnore);
+ updateHandler.setPackagesToIgnore(Process.myUserHandle(), packagesToIgnore);
}
- private void loadAllApps() {
- final List<UserHandle> profiles = mUserManager.getUserProfiles();
+ private void updateIconCacheForApps(IconCacheUpdateHandler updateHandler,
+ List<List<LauncherActivityInfo>> activityListPerUser) {
+ int userCount = activityListPerUser.size();
+ for (int i = 0; i < userCount; i++) {
+ updateHandler.updateIcons(activityListPerUser.get(i), LAUNCHER_ACTIVITY_INFO);
+ }
+ }
+ private List<List<LauncherActivityInfo>> loadAllApps() {
+ final List<UserHandle> profiles = mUserManager.getUserProfiles();
+ List<List<LauncherActivityInfo>> activityListPerUser = new ArrayList<>();
// Clear the list of apps
mBgAllAppsList.clear();
for (UserHandle user : profiles) {
@@ -806,7 +818,7 @@
// Fail if we don't have any apps
// TODO: Fix this. Only fail for the current user.
if (apps == null || apps.isEmpty()) {
- return;
+ return activityListPerUser;
}
boolean quietMode = mUserManager.isQuietModeEnabled(user);
// Create the ApplicationInfos
@@ -815,6 +827,7 @@
// This builds the icon bitmaps.
mBgAllAppsList.add(new AppInfo(app, user, quietMode), app);
}
+ activityListPerUser.add(apps);
}
if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) {
@@ -827,6 +840,7 @@
}
mBgAllAppsList.added = new ArrayList<>();
+ return activityListPerUser;
}
private void loadDeepShortcuts() {
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index c556083..0af5311 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -24,7 +24,7 @@
import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppInfo;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 448ff6c..82f4fe1 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -11,7 +11,7 @@
import android.util.Log;
import com.android.launcher3.AppFilter;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 5c0e259..78627ec 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -179,7 +179,7 @@
@Override
- public boolean onDrag(float displacement, float velocity) {
+ public boolean onDrag(float displacement) {
setContentTranslation(canChildBeDismissed()
? displacement : OverScroll.dampedScroll(displacement, getWidth()));
mContentTranslateAnimator.cancel();
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 7b09bcc..ce1cc89 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -256,7 +256,7 @@
}
@Override
- public boolean onDrag(float displacement, float velocity) {
+ public boolean onDrag(float displacement) {
float deltaProgress = mProgressMultiplier * (displacement - mDisplacementShift);
float progress = deltaProgress + mStartProgress;
updateProgress(progress);
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
index 6ffc0ef..a0a410e 100644
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ b/src/com/android/launcher3/touch/SwipeDetector.java
@@ -21,6 +21,7 @@
import android.graphics.PointF;
import android.util.Log;
import android.view.MotionEvent;
+import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import androidx.annotation.NonNull;
@@ -52,12 +53,6 @@
*/
public static final float RELEASE_VELOCITY_PX_MS = 1.0f;
- /**
- * The time constant used to calculate dampening in the low-pass filter of scroll velocity.
- * Cutoff frequency is set at 10 Hz.
- */
- public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10);
-
/* Scroll state, this is set to true during dragging and animation. */
private ScrollState mState = ScrollState.IDLE;
@@ -75,6 +70,8 @@
* Distance in pixels a touch can wander before we think the user is scrolling.
*/
abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
+
+ abstract float getVelocity(VelocityTracker tracker);
}
public static final Direction VERTICAL = new Direction() {
@@ -88,6 +85,11 @@
float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
return Math.abs(ev.getX(pointerIndex) - downPos.x);
}
+
+ @Override
+ float getVelocity(VelocityTracker tracker) {
+ return tracker.getYVelocity();
+ }
};
public static final Direction HORIZONTAL = new Direction() {
@@ -101,6 +103,11 @@
float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
return Math.abs(ev.getY(pointerIndex) - downPos.y);
}
+
+ @Override
+ float getVelocity(VelocityTracker tracker) {
+ return tracker.getXVelocity();
+ }
};
//------------------- ScrollState transition diagram -----------------------------------
@@ -151,16 +158,16 @@
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
- private Direction mDir;
+ private final Direction mDir;
private final float mTouchSlop;
+ private final float mMaxVelocity;
/* Client of this gesture detector can register a callback. */
private final Listener mListener;
- private long mCurrentMillis;
+ private VelocityTracker mVelocityTracker;
- private float mVelocity;
private float mLastDisplacement;
private float mDisplacement;
@@ -170,24 +177,22 @@
public interface Listener {
void onDragStart(boolean start);
- boolean onDrag(float displacement, float velocity);
+ boolean onDrag(float displacement);
void onDragEnd(float velocity, boolean fling);
}
public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
- this(ViewConfiguration.get(context).getScaledTouchSlop(), l, dir);
+ this(ViewConfiguration.get(context), l, dir);
}
@VisibleForTesting
- protected SwipeDetector(float touchSlope, @NonNull Listener l, @NonNull Direction dir) {
- mTouchSlop = touchSlope;
+ protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
+ @NonNull Direction dir) {
mListener = l;
mDir = dir;
- }
-
- public void updateDirection(Direction dir) {
- mDir = dir;
+ mTouchSlop = config.getScaledTouchSlop();
+ mMaxVelocity = config.getScaledMaximumFlingVelocity();
}
public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
@@ -215,14 +220,22 @@
}
public boolean onTouchEvent(MotionEvent ev) {
- switch (ev.getActionMasked()) {
+ int actionMasked = ev.getActionMasked();
+ if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
mLastDisplacement = 0;
mDisplacement = 0;
- mVelocity = 0;
if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
setState(ScrollState.DRAGGING);
@@ -247,8 +260,6 @@
break;
}
mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos);
- computeVelocity(mDir.getDisplacement(ev, pointerIndex, mLastPos),
- ev.getEventTime());
// handle state and listener calls.
if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) {
@@ -265,6 +276,8 @@
if (mState == ScrollState.DRAGGING) {
setState(ScrollState.SETTLING);
}
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
break;
default:
break;
@@ -308,55 +321,24 @@
private boolean reportDragging() {
if (mDisplacement != mLastDisplacement) {
if (DBG) {
- Log.d(TAG, String.format("onDrag disp=%.1f, velocity=%.1f",
- mDisplacement, mVelocity));
+ Log.d(TAG, String.format("onDrag disp=%.1f", mDisplacement));
}
mLastDisplacement = mDisplacement;
- return mListener.onDrag(mDisplacement - mSubtractDisplacement, mVelocity);
+ return mListener.onDrag(mDisplacement - mSubtractDisplacement);
}
return true;
}
private void reportDragEnd() {
+ mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
+ float velocity = mDir.getVelocity(mVelocityTracker) / 1000;
if (DBG) {
Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
- mDisplacement, mVelocity));
+ mDisplacement, velocity));
}
- mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
- }
-
- /**
- * Computes the damped velocity.
- */
- public float computeVelocity(float delta, long currentMillis) {
- long previousMillis = mCurrentMillis;
- mCurrentMillis = currentMillis;
-
- float deltaTimeMillis = mCurrentMillis - previousMillis;
- float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0;
- if (Math.abs(mVelocity) < 0.001f) {
- mVelocity = velocity;
- } else {
- float alpha = computeDampeningFactor(deltaTimeMillis);
- mVelocity = interpolate(mVelocity, velocity, alpha);
- }
- return mVelocity;
- }
-
- /**
- * Returns a time-dependent dampening factor using delta time.
- */
- private static float computeDampeningFactor(float deltaTime) {
- return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime);
- }
-
- /**
- * Returns the linear interpolation between two values
- */
- public static float interpolate(float from, float to, float alpha) {
- return (1.0f - alpha) * from + alpha * to;
+ mListener.onDragEnd(velocity, Math.abs(velocity) > RELEASE_VELOCITY_PX_MS);
}
public static long calculateDuration(float velocity, float progressNeeded) {
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 6688927..4de082e 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -17,6 +17,7 @@
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.ViewConfiguration.getLongPressTimeout;
@@ -29,6 +30,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
+import android.view.ViewConfiguration;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.CellLayout;
@@ -60,12 +62,16 @@
private final Launcher mLauncher;
private final Workspace mWorkspace;
private final PointF mTouchDownPoint = new PointF();
+ private final float mTouchSlop;
private int mLongPressState = STATE_CANCELLED;
public WorkspaceTouchListener(Launcher launcher, Workspace workspace) {
mLauncher = launcher;
mWorkspace = workspace;
+ // Use twice the touch slop as we are looking for long press which is more
+ // likely to cause movement.
+ mTouchSlop = 2 * ViewConfiguration.get(launcher).getScaledTouchSlop();
}
@Override
@@ -116,6 +122,9 @@
mWorkspace.onTouchEvent(ev);
if (mWorkspace.isHandlingTouch()) {
cancelLongPress();
+ } else if (action == ACTION_MOVE && PointF.length(
+ mTouchDownPoint.x - ev.getX(), mTouchDownPoint.y - ev.getY()) > mTouchSlop) {
+ cancelLongPress();
}
result = true;
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 26c8f24..f948beb 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -128,7 +128,7 @@
public void onDragStart(boolean start) { }
@Override
- public boolean onDrag(float displacement, float velocity) {
+ public boolean onDrag(float displacement) {
float range = mContent.getHeight();
displacement = Utilities.boundToRange(displacement, 0, range);
setTranslationShift(displacement / range);
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 29b4b0b..50db40f 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -33,8 +33,8 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.IconCache;
-import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.R;
diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
index 2ba672d..435125b 100644
--- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java
+++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
@@ -18,7 +18,7 @@
import android.util.Log;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator;
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 1016d04..a45521d 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -23,7 +23,7 @@
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.R;
import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.model.WidgetItem;
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index dd3e859..f4aa15a 100644
--- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -11,7 +11,6 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
-import android.content.pm.LauncherActivityInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
@@ -24,7 +23,8 @@
import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppFilter;
import com.android.launcher3.AppInfo;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.CachingLogic;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
@@ -203,9 +203,10 @@
}
@Override
- protected CacheEntry cacheLocked(
+ protected <T> CacheEntry cacheLocked(
@NonNull ComponentName componentName,
- @NonNull Provider<LauncherActivityInfo> infoProvider,
+ @NonNull Provider<T> infoProvider,
+ @NonNull CachingLogic<T> cachingLogic,
UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
if (entry == null) {
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 29e54b1..42a2764 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -10,7 +10,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
diff --git a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
index 73b6daf..b600473 100644
--- a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
@@ -19,7 +19,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import android.util.Log;
-import android.view.MotionEvent;
import android.view.ViewConfiguration;
import com.android.launcher3.testcomponent.TouchEventGenerator;
@@ -32,6 +31,7 @@
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyFloat;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -51,25 +51,28 @@
@Mock
private SwipeDetector.Listener mMockListener;
+ @Mock
+ private ViewConfiguration mMockConfig;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mGenerator = new TouchEventGenerator(new TouchEventGenerator.Listener() {
- @Override
- public void onTouchEvent(MotionEvent event) {
- mDetector.onTouchEvent(event);
- }
- });
+ mGenerator = new TouchEventGenerator((ev) -> mDetector.onTouchEvent(ev));
+ ViewConfiguration orgConfig = ViewConfiguration
+ .get(InstrumentationRegistry.getTargetContext());
+ doReturn(orgConfig.getScaledMaximumFlingVelocity()).when(mMockConfig)
+ .getScaledMaximumFlingVelocity();
- mDetector = new SwipeDetector(mTouchSlop, mMockListener, SwipeDetector.VERTICAL);
+ mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL);
mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
- mTouchSlop = ViewConfiguration.get(InstrumentationRegistry.getTargetContext())
- .getScaledTouchSlop();
+ mTouchSlop = orgConfig.getScaledTouchSlop();
+ doReturn(mTouchSlop).when(mMockConfig).getScaledTouchSlop();
+
L("mTouchSlop=", mTouchSlop);
}
@Test
- public void testDragStart_vertical() throws Exception {
+ public void testDragStart_vertical() {
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100, 100 + mTouchSlop);
// TODO: actually calculate the following parameters and do exact value checks.
@@ -77,7 +80,7 @@
}
@Test
- public void testDragStart_failed() throws Exception {
+ public void testDragStart_failed() {
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100 + mTouchSlop, 100);
// TODO: actually calculate the following parameters and do exact value checks.
@@ -85,8 +88,8 @@
}
@Test
- public void testDragStart_horizontal() throws Exception {
- mDetector = new SwipeDetector(mTouchSlop, mMockListener, SwipeDetector.HORIZONTAL);
+ public void testDragStart_horizontal() {
+ mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL);
mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
mGenerator.put(0, 100, 100);
@@ -96,15 +99,15 @@
}
@Test
- public void testDrag() throws Exception {
+ public void testDrag() {
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100, 100 + mTouchSlop);
// TODO: actually calculate the following parameters and do exact value checks.
- verify(mMockListener).onDrag(anyFloat(), anyFloat());
+ verify(mMockListener).onDrag(anyFloat());
}
@Test
- public void testDragEnd() throws Exception {
+ public void testDragEnd() {
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100, 100 + mTouchSlop);
mGenerator.move(0, 100, 100 + mTouchSlop * 2);
diff --git a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
index 23560e8..a50a8b1 100644
--- a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
@@ -42,7 +42,8 @@
LauncherActivityInfo settingsApp = getSettingsApp();
clearHomescreen();
- mActivityMonitor.startLauncher();
+ mDevice.pressHome();
+ mDevice.waitForIdle();
// Open all apps and wait for load complete.
final UiObject2 appsContainer = openAllApps();
diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
index d5b14f2..307a53e 100644
--- a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
+++ b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
@@ -29,7 +29,7 @@
import androidx.test.runner.AndroidJUnit4;
import android.view.LayoutInflater;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.WidgetPreviewLoader;