Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ab8e999f authored by Winson Chung's avatar Winson Chung
Browse files

Add multi-instance state to item infos

- Add legacy resource for supported multi-instance apps that
  matches the current SystemUI resource of the same name, and will
  be removed as apps migrate to the V manifest property to declare
  multi-instance support
- Load the multi-instance state from PackageManager when the db is
  first loaded or when packages are updated
- The multi-instance check is then used to determine if an app pair
  can be saved (ie. whether the action can be shown)

Bug: 323112914
Test: atest NexusLauncherTests

Merged-In: I565b4bee4ab5f7040910306b1fd60a4fc3bf9a1c
Change-Id: I9658b63672b692c42563c111c11be2433c98d535
parent 8355bc1a
Loading
Loading
Loading
Loading
+5 −6
Original line number Diff line number Diff line
@@ -322,14 +322,13 @@ public interface TaskShortcutFactory {
                    isLargeTileFocusedTask && isInExpectedScrollPosition;

            // No "save app pair" menu item if:
            // - app pairs feature is not enabled
            // - we are in 3p launcher
            // - the task in question is a single task
            // - the Overview Actions Button should be visible
            if (!FeatureFlags.enableAppPairs()
                    || !recentsView.supportsAppPairs()
                    || !taskView.containsMultipleTasks()
                    || shouldShowActionsButtonInstead) {
            // - the task view is not a valid save-able split pair
            if (!recentsView.supportsAppPairs()
                    || shouldShowActionsButtonInstead
                    || !recentsView.getSplitSelectController().getAppPairsController()
                            .canSaveAppPair(taskView)) {
                return null;
            }

+111 −26
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.isPersistentSnapPosition;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -38,6 +39,7 @@ import android.content.pm.PackageManager;
import android.util.Log;
import android.util.Pair;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

@@ -45,20 +47,25 @@ import com.android.internal.jank.Cuj;
import com.android.launcher3.Launcher;
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.config.FeatureFlags;
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.AppPairInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.TaskView;
@@ -87,18 +94,88 @@ public class AppPairsController {
    private Context mContext;
    private final SplitSelectStateController mSplitSelectStateController;
    private final StatsLogManager mStatsLogManager;
    private final String[] mLegacyMultiInstanceSupportedApps;

    public AppPairsController(Context context,
            SplitSelectStateController splitSelectStateController,
            StatsLogManager statsLogManager) {
        mContext = context;
        mSplitSelectStateController = splitSelectStateController;
        mStatsLogManager = statsLogManager;
        mLegacyMultiInstanceSupportedApps = mContext.getResources().getStringArray(
                R.array.config_appsSupportMultiInstancesSplit);
    }

    void onDestroy() {
        mContext = null;
    }

    /**
     * Returns whether the given component or its application supports multi-instance.
     */
    private boolean supportsMultiInstance(@NonNull ComponentName component) {
        // Check the legacy hardcoded allowlist first
        for (String pkg : mLegacyMultiInstanceSupportedApps) {
            if (pkg.equals(component.getPackageName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns whether two apps should be considered the same for multi-instance purposes, which
     * requires additional checks to ensure they can be started as multiple instances.
     */
    private boolean isSameAppForMultiInstance(@NonNull ItemInfo app1,
            @NonNull ItemInfo app2) {
        return app1.getTargetPackage().equals(app2.getTargetPackage())
                && app1.user.equals(app2.user);
    }

    /**
     * Returns whether the specified GroupedTaskView can be saved as an app pair.
     */
    public boolean canSaveAppPair(TaskView taskView) {
        if (mContext == null) {
            // Can ignore as the activity is already destroyed
            return false;
        }

        // Disallow saving app pairs if:
        // - app pairs feature is not enabled
        // - the task in question is a single task
        // - at least one app in app pair is unpinnable
        // - the task is not a GroupedTaskView
        // - both tasks in the GroupedTaskView are from the same app and the app does not
        //   support multi-instance
        if (!FeatureFlags.enableAppPairs()
                || !taskView.containsMultipleTasks()
                || !(taskView instanceof GroupedTaskView)) {
            return false;
        }

        GroupedTaskView gtv = (GroupedTaskView) taskView;
        TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers();
        WorkspaceItemInfo info1 = attributes[0].getItemInfo();
        WorkspaceItemInfo info2 = attributes[1].getItemInfo();
        AppInfo app1 = resolveAppInfoByComponent(info1.getComponentKey());
        AppInfo app2 = resolveAppInfoByComponent(info2.getComponentKey());

        if (app1 == null || app2 == null) {
            // Disallow saving app pairs for apps that don't have a front-door in Launcher
            return false;
        }

        if (isSameAppForMultiInstance(app1, app2)) {
            if (!supportsMultiInstance(app1.getTargetComponent()) 
                    || !supportsMultiInstance(app2.getTargetComponent())) {
                return false;
            }
        }
        return true;
    }

    /**
     * Creates a new app pair ItemInfo and adds it to the workspace.
     * <br>
@@ -118,20 +195,16 @@ public class AppPairsController {
        TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers();
        WorkspaceItemInfo recentsInfo1 = attributes[0].getItemInfo();
        WorkspaceItemInfo recentsInfo2 = attributes[1].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);
        WorkspaceItemInfo app1 = resolveAppPairWorkspaceInfo(recentsInfo1);
        WorkspaceItemInfo app2 = resolveAppPairWorkspaceInfo(recentsInfo2);

        if (app1 == null || app2 == null) {
            // This shouldn't happen if canSaveAppPair() is called above, but log an error and do
            // not create the app pair if the workspace items can't be resolved
            Log.w(TAG, "Failed to save app pair due to invalid apps ("
                    + "app1=" + recentsInfo1.getComponentKey().componentName
                    + " app2=" + recentsInfo2.getComponentKey().componentName + ")");
            return;
        }

        // WorkspaceItemProcessor won't process these new ItemInfos until the next launcher restart,
@@ -141,8 +214,9 @@ public class AppPairsController {

        @PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
        if (!isPersistentSnapPosition(snapPosition)) {
            // if we received an illegal snap position, log an error and do not create the app pair.
            Log.wtf(TAG, "tried to save an app pair with illegal snapPosition " + snapPosition);
            // If we received an illegal snap position, log an error and do not create the app pair
            Log.wtf(TAG, "Tried to save an app pair with illegal snapPosition "
                    + snapPosition);
            return;
        }

@@ -228,25 +302,36 @@ 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.
     * Returns an AppInfo associated with the app for the given ComponentKey, or null if no such
     * package exists in the AllAppsStore.
     */
    @Nullable
    private WorkspaceItemInfo lookupLaunchableItem(@Nullable ComponentKey key) {
        if (key == null) {
            return null;
        }

    private AppInfo resolveAppInfoByComponent(@NonNull ComponentKey key) {
        AllAppsStore appsStore = Launcher.getLauncher(mContext).getAppsView().getAppsStore();

        // Lookup by ComponentKey
        // First look up the app info in order of:
        // - The exact activity for the recent task
        // - The first(?) loaded activity from the package
        AppInfo appInfo = appsStore.getApp(key);
        if (appInfo == null) {
            // Lookup by package
            appInfo = appsStore.getApp(key, PACKAGE_KEY_COMPARATOR);
        }
        return appInfo;
    }

    /**
     * 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 resolveAppPairWorkspaceInfo(
            @NonNull WorkspaceItemInfo recentTaskInfo) {
        // ComponentKey should never be null (see TaskView#getItemInfo)
        AppInfo appInfo = resolveAppInfoByComponent(recentTaskInfo.getComponentKey());
        if (appInfo == null) {
            return null;
        }
        return appInfo != null ? appInfo.makeWorkspaceItem(mContext) : null;
    }

+7 −2
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.NavigationMode;
import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
import com.android.quickstep.util.AppPairsController;
import com.android.quickstep.util.LayoutUtils;

import java.lang.annotation.Retention;
@@ -133,6 +134,7 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
    protected DeviceProfile mDp;
    private final Rect mTaskSize = new Rect();
    private boolean mIsGroupedTask = false;
    private boolean mCanSaveAppPair = false;

    public OverviewActionsView(Context context) {
        this(context, null);
@@ -249,9 +251,12 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
     * Updates a batch of flags to hide and show actions buttons when a grouped task (split screen)
     * is focused.
     * @param isGroupedTask True if the focused task is a grouped task.
     * @param canSaveAppPair True if the focused task is a grouped task and can be saved as an app
     *                      pair.
     */
    public void updateForGroupedTask(boolean isGroupedTask) {
    public void updateForGroupedTask(boolean isGroupedTask, boolean canSaveAppPair) {
        mIsGroupedTask = isGroupedTask;
        mCanSaveAppPair = canSaveAppPair;
        updateActionButtonsVisibility();
    }

@@ -268,7 +273,7 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
    private void updateActionButtonsVisibility() {
        assert mDp != null;
        boolean showSingleTaskActions = !mIsGroupedTask;
        boolean showGroupActions = mIsGroupedTask && mDp.isTablet;
        boolean showGroupActions = mIsGroupedTask && mDp.isTablet && mCanSaveAppPair;
        getActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showSingleTaskActions ? 1 : 0);
        getGroupActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showGroupActions ? 1 : 0);
    }
+6 −2
Original line number Diff line number Diff line
@@ -4023,7 +4023,9 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
     * * Device is large screen
     */
    private void updateCurrentTaskActionsVisibility() {
        boolean isCurrentSplit = getCurrentPageTaskView() instanceof GroupedTaskView;
        TaskView taskView = getCurrentPageTaskView();
        boolean isCurrentSplit = taskView instanceof GroupedTaskView;
        GroupedTaskView groupedTaskView = isCurrentSplit ? (GroupedTaskView) taskView : null;
        // Update flags to see if entire actions bar should be hidden.
        if (!FeatureFlags.enableAppPairs()) {
            mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit);
@@ -4031,7 +4033,9 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
        mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive());
        // Update flags to see if actions bar should show buttons for a single task or a pair of
        // tasks.
        mActionsView.updateForGroupedTask(isCurrentSplit);
        boolean canSaveAppPair = isCurrentSplit && supportsAppPairs() &&
                getSplitSelectController().getAppPairsController().canSaveAppPair(groupedTaskView);
        mActionsView.updateForGroupedTask(isCurrentSplit, canSaveAppPair);

        if (isDesktopModeSupported()) {
            boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
+7 −0
Original line number Diff line number Diff line
@@ -274,4 +274,11 @@
    <string-array name="skip_private_profile_shortcut_packages" translatable="false">
        <item>com.android.settings</item>
    </string-array>

    <!-- Legacy list of components supporting multiple instances.
         DO NOT ADD TO THIS LIST.  Apps should use the PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
         property to declare multi-instance support in V+. This resource should match the resource
         of the same name in SystemUI. -->
    <string-array name="config_appsSupportMultiInstancesSplit">
    </string-array>
</resources>