Add IconCache support for deep shortcuts, loads deepshortcut on

background thread.  Added a feature flag to toggle on/off this
feature.

Bug: 140242324
Test:
  1. (Custom Shortcut) Long click on google maps -> widgets -> drag
     driving mode to workspace.

  2. Open chrome -> add to home screen -> add -> add automatically.

  3. InstallShortcutReceiver
     In Launcher.completeAddShortcut, commend out the code that
     calls PinRequestHelper.createWorkspaceItemFromPinItemRequest,
     then open chrome -> add to home screen -> add -> add
     automatically.

  4. ShortcutDragPreviewProvider
     qdb -> long press on suggested app that has deep shortcut
     -> drag to workspace.

Change-Id: If7babe4eddf5434909bf686b4e9bde15e444d9fd
This commit is contained in:
Pinyao Ting
2019-10-23 17:47:28 +00:00
parent 5d3dc9c76d
commit c1a1ced33a
11 changed files with 248 additions and 21 deletions

View File

@@ -17,6 +17,7 @@ package com.android.launcher3.icons.cache;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.LocaleList;
import android.os.UserHandle;
@@ -44,6 +45,13 @@ public interface CachingLogic<T> {
return null;
}
/**
* Returns the timestamp the entry was last updated in cache.
*/
default long getLastUpdatedTime(T object, PackageInfo info) {
return info.lastUpdateTime;
}
/**
* Returns true the object should be added to mem cache; otherwise returns false.
*/

View File

@@ -171,7 +171,8 @@ public class IconCacheUpdateHandler {
long updateTime = c.getLong(indexLastUpdate);
int version = c.getInt(indexVersion);
T app = componentMap.remove(component);
if (version == info.versionCode && updateTime == info.lastUpdateTime
if (version == info.versionCode
&& updateTime == cachingLogic.getLastUpdatedTime(app, info)
&& TextUtils.equals(c.getString(systemStateIndex),
mIconCache.getIconSystemState(info.packageName))) {
@@ -231,7 +232,6 @@ public class IconCacheUpdateHandler {
}
}
/**
* 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

View File

@@ -17,6 +17,7 @@
package com.android.launcher3;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.ShortcutUtil.fetchAndUpdateShortcutIconAsync;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
@@ -44,6 +45,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.LauncherIcons;
@@ -489,9 +491,13 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
return Pair.create(si, null);
} else if (shortcutInfo != null) {
WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, mContext);
LauncherIcons li = LauncherIcons.obtain(mContext);
itemInfo.bitmap = li.createShortcutIcon(shortcutInfo);
li.recycle();
if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
fetchAndUpdateShortcutIconAsync(mContext, itemInfo, shortcutInfo, true);
} else {
LauncherIcons li = LauncherIcons.obtain(mContext);
itemInfo.bitmap = li.createShortcutIcon(shortcutInfo);
li.recycle();
}
return Pair.create(itemInfo, shortcutInfo);
} else if (providerInfo != null) {
LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo

View File

@@ -127,6 +127,9 @@ public final class FeatureFlags {
public static final TogglableFlag ENABLE_HYBRID_HOTSEAT = new TogglableFlag(
"ENABLE_HYBRID_HOTSEAT", false, "Fill gaps in hotseat with predicted apps");
public static final TogglableFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = new TogglableFlag(
"ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache");
public static void initialize(Context context) {
// Avoid the disk read for user builds
if (Utilities.IS_DEBUG_DEVICE) {

View File

@@ -28,6 +28,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Process;
@@ -49,6 +50,7 @@ import com.android.launcher3.icons.cache.BaseIconCache;
import com.android.launcher3.icons.cache.CachingLogic;
import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageUserKey;
@@ -65,6 +67,7 @@ public class IconCache extends BaseIconCache {
private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
private final CachingLogic<ShortcutInfo> mShortcutCachingLogic;
private final LauncherApps mLauncherApps;
private final UserManagerCompat mUserManager;
@@ -78,6 +81,7 @@ public class IconCache extends BaseIconCache {
inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */);
mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
mShortcutCachingLogic = new ShortcutCachingLogic();
mLauncherApps = mContext.getSystemService(LauncherApps.class);
mUserManager = UserManagerCompat.getInstance(mContext);
mInstantAppResolver = InstantAppResolver.newInstance(mContext);
@@ -175,6 +179,14 @@ public class IconCache extends BaseIconCache {
getTitleAndIcon(info, () -> activityInfo, false, useLowResIcon);
}
/**
* Fill in info with the icon and label for deep shortcut.
*/
public synchronized CacheEntry getDeepShortcutTitleAndIcon(ShortcutInfo info) {
return cacheLocked(ShortcutKey.fromInfo(info).componentName, info.getUserHandle(),
() -> info, mShortcutCachingLogic, false, false);
}
/**
* Fill in {@param info} with the icon and label. If the
* corresponding activity is not found, it reverts to the package icon.

View File

@@ -25,12 +25,14 @@ import android.graphics.drawable.Drawable;
import android.os.Process;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.AppInfo;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.shortcuts.DeepShortcutManager;
@@ -112,7 +114,6 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
}
// below methods should also migrate to BaseIconFactory
public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo) {
return createShortcutIcon(shortcutInfo, true /* badged */);
}
@@ -121,12 +122,20 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
return createShortcutIcon(shortcutInfo, badged, null);
}
public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo,
boolean badged, @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo, boolean badged,
@Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
return createShortcutIconCached(shortcutInfo, badged, true, fallbackIconProvider);
} else {
return createShortcutIconLegacy(shortcutInfo, badged, fallbackIconProvider);
}
}
public BitmapInfo createShortcutIconLegacy(ShortcutInfo shortcutInfo, boolean badged,
@Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext)
.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
final Bitmap unbadgedBitmap;
if (unbadgedDrawable != null) {
unbadgedBitmap = createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
@@ -155,6 +164,44 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
return BitmapInfo.of(icon, badge.bitmap.color);
}
@WorkerThread
public BitmapInfo createShortcutIconCached(ShortcutInfo shortcutInfo, boolean badged,
boolean useCache, @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
final BitmapInfo bitmapInfo;
if (useCache) {
bitmapInfo = cache.getDeepShortcutTitleAndIcon(shortcutInfo).bitmap;
} else {
bitmapInfo = new ShortcutCachingLogic().loadIcon(mContext, shortcutInfo);
}
final Bitmap unbadgedBitmap;
if (bitmapInfo.icon != null) {
unbadgedBitmap = bitmapInfo.icon;
} else {
if (fallbackIconProvider != null) {
// Fallback icons are already badged and with appropriate shadow
ItemInfoWithIcon fullIcon = fallbackIconProvider.get();
if (fullIcon != null && fullIcon.bitmap != null) {
return fullIcon.bitmap;
}
}
unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()).icon;
}
if (!badged) {
return BitmapInfo.of(unbadgedBitmap, Themes.getColorAccent(mContext));
}
final Bitmap unbadgedfinal = unbadgedBitmap;
final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
Bitmap icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {
getShadowGenerator().recreateIcon(unbadgedfinal, c);
badgeWithDrawable(c, new FastBitmapDrawable(badge.bitmap));
});
return BitmapInfo.of(icon, badge.bitmap.color);
}
public ItemInfoWithIcon getShortcutInfoBadge(ShortcutInfo shortcutInfo, IconCache cache) {
ComponentName cn = shortcutInfo.getActivity();
String badgePkg = shortcutInfo.getPackage();

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2019 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.PackageInfo;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import androidx.annotation.NonNull;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.cache.CachingLogic;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.Themes;
/**
* Caching logic for shortcuts.
*/
public class ShortcutCachingLogic implements CachingLogic<ShortcutInfo> {
@Override
public ComponentName getComponent(ShortcutInfo info) {
return ShortcutKey.fromInfo(info).componentName;
}
@Override
public UserHandle getUser(ShortcutInfo info) {
return info.getUserHandle();
}
@Override
public CharSequence getLabel(ShortcutInfo info) {
return info.getShortLabel();
}
@NonNull
@Override
public BitmapInfo loadIcon(Context context, ShortcutInfo info) {
try (LauncherIcons li = LauncherIcons.obtain(context)) {
Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
.getShortcutIconDrawable(info, LauncherAppState.getIDP(context).fillResIconDpi);
if (unbadgedDrawable == null) return BitmapInfo.LOW_RES_INFO;
return new BitmapInfo(li.createScaledBitmapWithoutShadow(
unbadgedDrawable, 0), Themes.getColorAccent(context));
}
}
@Override
public long getLastUpdatedTime(ShortcutInfo shortcutInfo, PackageInfo info) {
if (shortcutInfo == null || !FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
return info.lastUpdateTime;
}
return Math.max(shortcutInfo.getLastChangedTimestamp(), info.lastUpdateTime);
}
@Override
public boolean addToMemCache() {
return false;
}
}

View File

@@ -63,6 +63,7 @@ import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherActivityCachingLogic;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.ShortcutCachingLogic;
import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.pm.PackageInstallInfo;
@@ -71,7 +72,6 @@ import com.android.launcher3.provider.ImportDataTask;
import com.android.launcher3.qsb.QsbContainerView;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IOUtils;
import com.android.launcher3.util.LooperIdleLock;
@@ -174,7 +174,8 @@ public class LoaderTask implements Runnable {
Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
TimingLogger logger = new TimingLogger(TAG, "run");
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
loadWorkspace();
List<ShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts);
logger.addSplit("loadWorkspace");
verifyNotStopped();
@@ -206,19 +207,33 @@ public class LoaderTask implements Runnable {
mApp.getModel()::onPackageIconsUpdated);
logger.addSplit("update icon cache");
if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
verifyNotStopped();
logger.addSplit("save shortcuts in icon cache");
updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
mApp.getModel()::onPackageIconsUpdated);
}
// Take a break
waitForIdle();
logger.addSplit("step 2 complete");
verifyNotStopped();
// third step
loadDeepShortcuts();
List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
logger.addSplit("loadDeepShortcuts");
verifyNotStopped();
mResults.bindDeepShortcuts();
logger.addSplit("bindDeepShortcuts");
if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
verifyNotStopped();
logger.addSplit("save deep shortcuts in icon cache");
updateHandler.updateIcons(allDeepShortcuts,
new ShortcutCachingLogic(), (pkgs, user) -> { });
}
// Take a break
waitForIdle();
logger.addSplit("step 3 complete");
@@ -256,7 +271,7 @@ public class LoaderTask implements Runnable {
this.notify();
}
private void loadWorkspace() {
private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
final Context context = mApp.getContext();
final ContentResolver contentResolver = context.getContentResolver();
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
@@ -512,6 +527,7 @@ public class LoaderTask implements Runnable {
info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
}
intent = info.intent;
allDeepShortcuts.add(pinnedShortcut);
} else {
// Create a shortcut info in disabled mode for now.
info = c.loadSimpleWorkspaceItem();
@@ -860,7 +876,8 @@ public class LoaderTask implements Runnable {
return allActivityList;
}
private void loadDeepShortcuts() {
private List<ShortcutInfo> loadDeepShortcuts() {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
mBgDataModel.deepShortcutMap.clear();
mBgDataModel.hasShortcutHostPermission = mShortcutManager.hasHostPermission();
if (mBgDataModel.hasShortcutHostPermission) {
@@ -868,10 +885,12 @@ public class LoaderTask implements Runnable {
if (mUserManager.isUserUnlocked(user)) {
List<ShortcutInfo> shortcuts =
mShortcutManager.queryForAllShortcuts(user);
allShortcuts.addAll(shortcuts);
mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts);
}
}
}
return allShortcuts;
}
public static boolean isValidProvider(AppWidgetProviderInfo provider) {

View File

@@ -17,6 +17,7 @@
package com.android.launcher3.pm;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.ShortcutUtil.fetchAndUpdateShortcutIconAsync;
import android.annotation.TargetApi;
import android.content.Context;
@@ -31,6 +32,7 @@ import androidx.annotation.Nullable;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.LauncherIcons;
public class PinRequestHelper {
@@ -81,11 +83,15 @@ public class PinRequestHelper {
ShortcutInfo si = request.getShortcutInfo();
WorkspaceItemInfo info = new WorkspaceItemInfo(si, context);
// Apply the unbadged icon and fetch the actual icon asynchronously.
LauncherIcons li = LauncherIcons.obtain(context);
info.bitmap = li.createShortcutIcon(si, false /* badged */);
li.recycle();
LauncherAppState.getInstance(context).getModel()
.updateAndBindWorkspaceItem(info, si);
if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
fetchAndUpdateShortcutIconAsync(context, info, si, false);
} else {
LauncherIcons li = LauncherIcons.obtain(context);
info.bitmap = li.createShortcutIcon(si, false /* badged */);
li.recycle();
LauncherAppState.getInstance(context).getModel()
.updateAndBindWorkspaceItem(info, si);
}
return info;
} else {
return null;

View File

@@ -25,7 +25,9 @@ import android.view.View;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.icons.BitmapRenderer;
/**
* Extension of {@link DragPreviewProvider} which generates bitmaps scaled to the default icon size.
@@ -39,16 +41,27 @@ public class ShortcutDragPreviewProvider extends DragPreviewProvider {
mPositionShift = shift;
}
@Override
public Bitmap createDragBitmap() {
if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
return BitmapRenderer.createHardwareBitmap(
size + blurSizeOutline,
size + blurSizeOutline,
(c) -> drawDragViewOnBackground(c, size));
} else {
return createDragBitmapLegacy();
}
}
private Bitmap createDragBitmapLegacy() {
Drawable d = mView.getBackground();
Rect bounds = getDrawableBounds(d);
int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
final Bitmap b = Bitmap.createBitmap(
size + blurSizeOutline,
size + blurSizeOutline,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(b);
canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2);
canvas.scale(((float) size) / bounds.width(), ((float) size) / bounds.height(), 0, 0);
@@ -57,6 +70,16 @@ public class ShortcutDragPreviewProvider extends DragPreviewProvider {
return b;
}
private void drawDragViewOnBackground(Canvas canvas, float size) {
Drawable d = mView.getBackground();
Rect bounds = getDrawableBounds(d);
canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2);
canvas.scale(size / bounds.width(), size / bounds.height(), 0, 0);
canvas.translate(bounds.left, bounds.top);
d.draw(canvas);
}
@Override
public float getScaleAndPosition(Bitmap preview, int[] outPos) {
Launcher launcher = Launcher.getLauncher(mView.getContext());

View File

@@ -15,10 +15,19 @@
*/
package com.android.launcher3.util;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import androidx.annotation.NonNull;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.shortcuts.ShortcutKey;
@@ -61,6 +70,21 @@ public class ShortcutUtil {
&& info instanceof WorkspaceItemInfo;
}
/**
* Fetch the shortcut icon in background, then update the UI.
*/
public static void fetchAndUpdateShortcutIconAsync(
@NonNull Context context, @NonNull WorkspaceItemInfo info, @NonNull ShortcutInfo si,
boolean badged) {
MODEL_EXECUTOR.execute(() -> {
try (LauncherIcons li = LauncherIcons.obtain(context)) {
info.bitmap = li.createShortcutIcon(si, badged, null);
LauncherAppState.getInstance(context).getModel()
.updateAndBindWorkspaceItem(info, si);
}
});
}
private static boolean isActive(ItemInfo info) {
boolean isLoading = info instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) info).hasPromiseIconUi();