diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java index b8463230fc..ce8df9bdff 100644 --- a/quickstep/src/com/android/quickstep/util/AppPairsController.java +++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java @@ -20,6 +20,7 @@ package com.android.quickstep.util; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_LAUNCH; +import static com.android.launcher3.model.data.AppInfo.PACKAGE_KEY_COMPARATOR; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; @@ -30,6 +31,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.isPersisten import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.LauncherApps; import android.util.Log; import android.util.Pair; @@ -42,10 +44,12 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; +import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.apppairs.AppPairIcon; import com.android.launcher3.icons.IconCache; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager; +import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -93,14 +97,38 @@ public class AppPairsController { } /** - * Creates a new app pair ItemInfo and adds it to the workspace + * Creates a new app pair ItemInfo and adds it to the workspace. + *
+ * We create WorkspaceItemInfos to save onto the app pair in the following way: + *
1. We verify that the ComponentKey from our Recents tile corresponds to a real + * launchable app in the app store. + *
2. If it doesn't, we search for the underlying launchable app via package name, and use + * that instead. + *
3. If that fails, we re-use the existing WorkspaceItemInfo by cloning it and replacing + * its intent with one from PackageManager. + *
4. If everything fails, we just use the WorkspaceItemInfo as is, with its existing + * intent. This is not preferred, but will still work in most cases (notably it will not work + * well on trampoline apps). */ public void saveAppPair(GroupedTaskView gtv) { TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers(); - WorkspaceItemInfo app1 = attributes[0].getItemInfo().clone(); - WorkspaceItemInfo app2 = attributes[1].getItemInfo().clone(); - app1.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; - app2.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + WorkspaceItemInfo recentsInfo1 = attributes[0].getItemInfo(); + WorkspaceItemInfo recentsInfo2 = attributes[0].getItemInfo(); + WorkspaceItemInfo app1 = lookupLaunchableItem(recentsInfo1.getComponentKey()); + WorkspaceItemInfo app2 = lookupLaunchableItem(recentsInfo2.getComponentKey()); + + // If app lookup fails, use the WorkspaceItemInfo that we have, but try to override default + // intent with one from PackageManager. + if (app1 == null) { + Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo1.title + + " failed. Falling back to the WorkspaceItemInfo from Recents."); + app1 = convertRecentsItemToAppItem(recentsInfo1); + } + if (app2 == null) { + Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo2.title + + " failed. Falling back to the WorkspaceItemInfo from Recents."); + app2 = convertRecentsItemToAppItem(recentsInfo2); + } @PersistentSnapPosition int snapPosition = gtv.getSnapPosition(); if (!isPersistentSnapPosition(snapPosition)) { @@ -188,6 +216,52 @@ public class AppPairsController { ); } + /** + * Creates a new launchable WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION by looking the + * ComponentKey up in the AllAppsStore. If no app is found, attempts a lookup by package + * instead. If that lookup fails, returns null. + */ + @Nullable + private WorkspaceItemInfo lookupLaunchableItem(@Nullable ComponentKey key) { + if (key == null) { + return null; + } + + AllAppsStore appsStore = Launcher.getLauncher(mContext).getAppsView().getAppsStore(); + + // Lookup by ComponentKey + AppInfo appInfo = appsStore.getApp(key); + if (appInfo == null) { + // Lookup by package + appInfo = appsStore.getApp(key, PACKAGE_KEY_COMPARATOR); + } + + return appInfo != null ? appInfo.makeWorkspaceItem(mContext) : null; + } + + /** + * Converts a WorkspaceItemInfo of itemType=ITEM_TYPE_TASK (from a Recents task) to a new + * WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION. + */ + private WorkspaceItemInfo convertRecentsItemToAppItem(WorkspaceItemInfo recentsItem) { + if (recentsItem.itemType != LauncherSettings.Favorites.ITEM_TYPE_TASK) { + Log.w(TAG, "Expected ItemInfo of type ITEM_TYPE_TASK, but received " + + recentsItem.itemType); + } + + WorkspaceItemInfo launchableItem = recentsItem.clone(); + PackageManager p = mContext.getPackageManager(); + Intent launchIntent = p.getLaunchIntentForPackage(recentsItem.getTargetPackage()); + Log.w(TAG, "Initial intent from Recents: " + launchableItem.intent + "\n" + + "Intent from PackageManager: " + launchIntent); + if (launchIntent != null) { + // If lookup from PackageManager fails, just use the existing intent + launchableItem.intent = launchIntent; + } + launchableItem.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + return launchableItem; + } + /** * Handles the complicated logic for how to animate an app pair entrance when already inside an * app or app pair. diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java index 009a2aa6b7..9623709348 100644 --- a/src/com/android/launcher3/allapps/AllAppsStore.java +++ b/src/com/android/launcher3/allapps/AllAppsStore.java @@ -40,6 +40,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; @@ -138,12 +139,22 @@ public class AllAppsStore { /** * Returns {@link AppInfo} if any apps matches with provided {@link ComponentKey}, otherwise * null. + * + * Uses {@link AppInfo#COMPONENT_KEY_COMPARATOR} as a default comparator. */ @Nullable public AppInfo getApp(ComponentKey key) { + return getApp(key, COMPONENT_KEY_COMPARATOR); + } + + /** + * Generic version of {@link #getApp(ComponentKey)} that allows comparator to be specified. + */ + @Nullable + public AppInfo getApp(ComponentKey key, Comparator comparator) { mTempInfo.componentName = key.componentName; mTempInfo.user = key.user; - int index = Arrays.binarySearch(mApps, mTempInfo, COMPONENT_KEY_COMPARATOR); + int index = Arrays.binarySearch(mApps, mTempInfo, comparator); return index < 0 ? null : mApps[index]; } diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java index b213fe306e..210d720bca 100644 --- a/src/com/android/launcher3/model/data/AppInfo.java +++ b/src/com/android/launcher3/model/data/AppInfo.java @@ -52,6 +52,9 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory { return uc != 0 ? uc : a.componentName.compareTo(b.componentName); }; + public static final Comparator PACKAGE_KEY_COMPARATOR = Comparator.comparingInt( + (AppInfo a) -> a.user.hashCode()).thenComparing(ItemInfo::getTargetPackage); + /** * The intent used to start the application. */