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

Commit a6c11721 authored by Winson Chung's avatar Winson Chung Committed by Android (Google) Code Review
Browse files

Merge "Add property for apps to request support for showing multi-instance entry points" into main

parents 2a7f55af 246ee62f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -53974,6 +53974,7 @@ package android.view {
    field public static final String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS = "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
    field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
    field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
    field @FlaggedApi("com.android.window.flags.supports_multi_instance_system_ui") public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
  }
  public static class WindowManager.BadTokenException extends java.lang.RuntimeException {
+24 −0
Original line number Diff line number Diff line
@@ -1493,6 +1493,30 @@ public interface WindowManager extends ViewManager {
    String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED =
            "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";

    /**
     * Activity or Application level {@link android.content.pm.PackageManager.Property
     * PackageManager.Property} for an app to declare that System UI should be shown for this
     * app/component to allow it to be launched as multiple instances.  This property only affects
     * SystemUI behavior and does _not_ affect whether a component can actually be launched into
     * multiple instances, which is determined by the Activity's {@code launchMode} or the launching
     * Intent's flags.  If the property is set on the Application, then all components within that
     * application will use that value unless specified per component.
     *
     * The value must be a boolean string.
     *
     * <p><b>Syntax:</b>
     * <pre>
     * &lt;activity&gt;
     *   &lt;property
     *     android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
     *     android:value="true|false"/&gt;
     * &lt;/activity&gt;
     * </pre>
     */
    @FlaggedApi(Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI)
    public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI =
            "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";

    /**
     * Request for app's keyboard shortcuts to be retrieved asynchronously.
     *
+8 −0
Original line number Diff line number Diff line
@@ -75,3 +75,11 @@ flag {
    bug: "259497289"
    is_fixed_read_only: true
}

flag {
    name: "supports_multi_instance_system_ui"
    namespace: "multitasking"
    description: "Feature flag to enable a multi-instance system ui component property."
    bug: "262864589"
    is_fixed_read_only: true
}
 No newline at end of file
+39 −3
Original line number Diff line number Diff line
@@ -16,7 +16,10 @@

package com.android.wm.shell.common.split;

import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_PERSONS_DATA;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;

import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
@@ -24,19 +27,27 @@ 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.SPLIT_POSITION_UNDEFINED;

import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.UserHandle;

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

import com.android.internal.util.ArrayUtils;
import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;

import java.util.Arrays;
import java.util.List;

/** Helper utility class for split screen components to use. */
public class SplitScreenUtils {
    /** Reverse the split position. */
@@ -135,4 +146,29 @@ public class SplitScreenUtils {
            return isLandscape;
        }
    }

    /** Returns the component from a PendingIntent */
    @Nullable
    public static ComponentName getComponent(@Nullable PendingIntent pendingIntent) {
        if (pendingIntent == null || pendingIntent.getIntent() == null) {
            return null;
        }
        return pendingIntent.getIntent().getComponent();
    }

    /** Returns the component from a shortcut */
    @Nullable
    public static ComponentName getShortcutComponent(@NonNull String packageName, String shortcutId,
            @NonNull UserHandle user, @NonNull LauncherApps launcherApps) {
        LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
        query.setPackage(packageName);
        query.setShortcutIds(Arrays.asList(shortcutId));
        query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
                | FLAG_MATCH_CACHED | FLAG_GET_PERSONS_DATA);
        List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(query, user);
        ShortcutInfo info = shortcuts != null && shortcuts.size() > 0
                ? shortcuts.get(0)
                : null;
        return info != null ? info.getActivity() : null;
    }
}
+81 −21
Original line number Diff line number Diff line
@@ -23,12 +23,15 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI;

import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.common.split.SplitScreenUtils.getComponent;
import static com.android.wm.shell.common.split.SplitScreenUtils.getShortcutComponent;
import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
@@ -47,6 +50,8 @@ import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Bundle;
@@ -171,6 +176,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
    private final ShellTaskOrganizer mTaskOrganizer;
    private final SyncTransactionQueue mSyncQueue;
    private final Context mContext;
    private final PackageManager mPackageManager;
    private final LauncherApps mLauncherApps;
    private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
    private final ShellExecutor mMainExecutor;
    private final SplitScreenImpl mImpl = new SplitScreenImpl();
@@ -186,7 +193,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
    private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
    private final Optional<DesktopTasksController> mDesktopTasksController;
    private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
    private final String[] mAppsSupportMultiInstances;
    // A static allow list of apps which support multi-instance
    private final String[] mAppsSupportingMultiInstance;

    @VisibleForTesting
    StageCoordinator mStageCoordinator;
@@ -220,6 +228,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        mTaskOrganizer = shellTaskOrganizer;
        mSyncQueue = syncQueue;
        mContext = context;
        mPackageManager = context.getPackageManager();
        mLauncherApps = context.getSystemService(LauncherApps.class);
        mRootTDAOrganizer = rootTDAOrganizer;
        mMainExecutor = mainExecutor;
        mDisplayController = displayController;
@@ -242,7 +252,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,

        // TODO(255224696): Remove the config once having a way for client apps to opt-in
        //                  multi-instances split.
        mAppsSupportMultiInstances = mContext.getResources()
        mAppsSupportingMultiInstance = mContext.getResources()
                .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
    }

@@ -266,12 +276,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
            WindowDecorViewModel windowDecorViewModel,
            DesktopTasksController desktopTasksController,
            ShellExecutor mainExecutor,
            StageCoordinator stageCoordinator) {
            StageCoordinator stageCoordinator,
            String[] appsSupportingMultiInstance) {
        mShellCommandHandler = shellCommandHandler;
        mShellController = shellController;
        mTaskOrganizer = shellTaskOrganizer;
        mSyncQueue = syncQueue;
        mContext = context;
        mPackageManager = context.getPackageManager();
        mLauncherApps = context.getSystemService(LauncherApps.class);
        mRootTDAOrganizer = rootTDAOrganizer;
        mMainExecutor = mainExecutor;
        mDisplayController = displayController;
@@ -288,8 +301,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        mStageCoordinator = stageCoordinator;
        mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
        shellInit.addInitCallback(this::onInit, this);
        mAppsSupportMultiInstances = mContext.getResources()
                .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
        mAppsSupportingMultiInstance = appsSupportingMultiInstance;
    }

    public SplitScreen asSplitScreen() {
@@ -588,7 +600,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,

        if (samePackage(packageName, getPackageName(reverseSplitPosition(position)),
                user.getIdentifier(), getUserId(reverseSplitPosition(position)))) {
            if (supportMultiInstancesSplit(packageName)) {
            if (supportsMultiInstanceSplit(getShortcutComponent(packageName, shortcutId, user,
                    mLauncherApps))) {
                activityOptions.setApplyMultipleTaskFlagForShortcut(true);
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
            } else if (isSplitScreenVisible()) {
@@ -609,7 +622,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
                activityOptions.toBundle(), user);
    }

    void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
    void startShortcutAndTaskWithLegacyTransition(@NonNull ShortcutInfo shortcutInfo,
            @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
            @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
            RemoteAnimationAdapter adapter, InstanceId instanceId) {
@@ -621,7 +634,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        final int userId1 = shortcutInfo.getUserId();
        final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
        if (samePackage(packageName1, packageName2, userId1, userId2)) {
            if (supportMultiInstancesSplit(shortcutInfo.getPackage())) {
            if (supportsMultiInstanceSplit(shortcutInfo.getActivity())) {
                activityOptions.setApplyMultipleTaskFlagForShortcut(true);
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
            } else {
@@ -640,7 +653,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
                instanceId);
    }

    void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
    void startShortcutAndTask(@NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options1,
            int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
            @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
            InstanceId instanceId) {
@@ -653,7 +666,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        final int userId1 = shortcutInfo.getUserId();
        final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
        if (samePackage(packageName1, packageName2, userId1, userId2)) {
            if (supportMultiInstancesSplit(packageName1)) {
            if (supportsMultiInstanceSplit(shortcutInfo.getActivity())) {
                activityOptions.setApplyMultipleTaskFlagForShortcut(true);
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
            } else {
@@ -692,7 +705,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
        final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
        if (samePackage(packageName1, packageName2, userId1, userId2)) {
            if (supportMultiInstancesSplit(packageName1)) {
            if (supportsMultiInstanceSplit(getComponent(pendingIntent))) {
                fillInIntent = new Intent();
                fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
@@ -722,7 +735,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
        boolean setSecondIntentMultipleTask = false;
        if (samePackage(packageName1, packageName2, userId1, userId2)) {
            if (supportMultiInstancesSplit(packageName1)) {
            if (supportsMultiInstanceSplit(getComponent(pendingIntent))) {
                setSecondIntentMultipleTask = true;
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
            } else {
@@ -757,7 +770,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
        final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
        if (samePackage(packageName1, packageName2, userId1, userId2)) {
            if (supportMultiInstancesSplit(packageName1)) {
            if (supportsMultiInstanceSplit(getComponent(pendingIntent1))) {
                fillInIntent1 = new Intent();
                fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                fillInIntent2 = new Intent();
@@ -794,7 +807,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
                ? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic();
        boolean setSecondIntentMultipleTask = false;
        if (samePackage(packageName1, packageName2, userId1, userId2)) {
            if (supportMultiInstancesSplit(packageName1)) {
            if (supportsMultiInstanceSplit(getComponent(pendingIntent1))) {
                fillInIntent1 = new Intent();
                fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                setSecondIntentMultipleTask = true;
@@ -856,7 +869,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
            return;
        }
        if (samePackage(packageName1, packageName2, userId1, userId2)) {
            if (supportMultiInstancesSplit(packageName1)) {
            if (supportsMultiInstanceSplit(getComponent(intent))) {
                // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
                // the split and there is no reusable background task.
                fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -915,16 +928,63 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        return taskInfo != null ? taskInfo.userId : -1;
    }

    /**
     * Returns whether a specific component desires to be launched in multiple instances for
     * split screen.
     */
    @VisibleForTesting
    boolean supportMultiInstancesSplit(String packageName) {
        if (packageName != null) {
            for (int i = 0; i < mAppsSupportMultiInstances.length; i++) {
                if (mAppsSupportMultiInstances[i].equals(packageName)) {
    boolean supportsMultiInstanceSplit(@Nullable ComponentName componentName) {
        if (componentName == null || componentName.getPackageName() == null) {
            // TODO(b/262864589): Handle empty component case
            return false;
        }

        // Check the pre-defined allow list
        final String packageName = componentName.getPackageName();
        for (int i = 0; i < mAppsSupportingMultiInstance.length; i++) {
            if (mAppsSupportingMultiInstance[i].equals(packageName)) {
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                        "application=%s in allowlist supports multi-instance", packageName);
                return true;
            }
        }

        // Check the activity property first
        try {
            final PackageManager.Property activityProp = mPackageManager.getProperty(
                    PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName);
            // If the above call doesn't throw a NameNotFoundException, then the activity property
            // should override the application property value
            if (activityProp.isBoolean()) {
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                        "activity=%s supports multi-instance", componentName);
                return activityProp.getBoolean();
            } else {
                ProtoLog.w(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                        "Warning: property=%s for activity=%s has non-bool type=%d",
                        PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName,
                        activityProp.getType());
            }
        } catch (PackageManager.NameNotFoundException nnfe) {
            // Not specified in the activity, fall through
        }

        // Check the application property otherwise
        try {
            final PackageManager.Property appProp = mPackageManager.getProperty(
                    PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName);
            if (appProp.isBoolean()) {
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                        "application=%s supports multi-instance", packageName);
                return appProp.getBoolean();
            } else {
                ProtoLog.w(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                        "Warning: property=%s for application=%s has non-bool type=%d",
                        PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, appProp.getType());
            }
        } catch (PackageManager.NameNotFoundException nnfe) {
            // Not specified in either application or activity
        }
        return false;
    }

Loading