From 1a98a9d8748de9f0fe68b4379854d8d4741b0707 Mon Sep 17 00:00:00 2001 From: Jeremy Sim Date: Wed, 21 Feb 2024 14:24:18 -0800 Subject: [PATCH] Fix app pair launches with certain apps This CL changes the way app pairs are saved. Previously, we saved the app pair using WorkspaceItemInfos directly from Recents, which caused certain apps to fail on launch because they weren't being launched with the correct Intent. Now, we look up a proper launchable WorkspaceItemInfo from the AllAppsStore and save that to the app pair, which should fix the launch issues. Bug: 323112914 Bug: 323110399 Test: Can launch Slides, Play Store, and Calendar when part of an app pair. Flag: ACONFIG com.android.wm.shell.enable_app_pairs TEAMFOOD Change-Id: I475afa1a237c50ffb0ffaf85af43912a803b8011 --- .../quickstep/util/AppPairsController.java | 84 +++++++++++++++++-- .../launcher3/allapps/AllAppsStore.java | 13 ++- .../android/launcher3/model/data/AppInfo.java | 3 + 3 files changed, 94 insertions(+), 6 deletions(-) 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. */