Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java +35 −3 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ package com.android.wm.shell.common.split; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED; import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; Loading @@ -24,19 +24,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. */ Loading Loading @@ -135,4 +143,28 @@ 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_ALL_KINDS_WITH_ALL_PINNED); List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(query, user); ShortcutInfo info = shortcuts != null && shortcuts.size() > 0 ? shortcuts.get(0) : null; return info != null ? info.getActivity() : null; } } libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +81 −21 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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(); Loading @@ -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; Loading Loading @@ -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; Loading @@ -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); } Loading @@ -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; Loading @@ -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() { Loading Loading @@ -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()) { Loading @@ -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) { Loading @@ -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 { Loading @@ -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) { Loading @@ -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 { Loading Loading @@ -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"); Loading Loading @@ -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 { Loading Loading @@ -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(); Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenUtilsTests.kt 0 → 100644 +69 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.wm.shell.common.split import android.content.ComponentName import android.content.pm.LauncherApps import android.content.pm.ShortcutInfo import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.wm.shell.ShellTestCase import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.mock import org.mockito.Mockito.`when` @RunWith(AndroidJUnit4::class) class SplitScreenUtilsTests : ShellTestCase() { @Test fun getShortcutComponent_nullShortcuts() { val launcherApps = mock(LauncherApps::class.java).also { `when`(it.getShortcuts(any(), any())).thenReturn(null) } assertEquals(null, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE, TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps)) } @Test fun getShortcutComponent_noShortcuts() { val launcherApps = mock(LauncherApps::class.java).also { `when`(it.getShortcuts(any(), any())).thenReturn(ArrayList<ShortcutInfo>()) } assertEquals(null, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE, TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps)) } @Test fun getShortcutComponent_validShortcut() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val shortcutInfo = ShortcutInfo.Builder(context, "id").setActivity(component).build() val launcherApps = mock(LauncherApps::class.java).also { `when`(it.getShortcuts(any(), any())).thenReturn(arrayListOf(shortcutInfo)) } assertEquals(component, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE, TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps)) } companion object { val TEST_PACKAGE = "com.android.wm.shell.common.split" val TEST_ACTIVITY = "TestActivity"; val TEST_SHORTCUT_ID = "test_shortcut_1" } } No newline at end of file libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +143 −5 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java +35 −3 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ package com.android.wm.shell.common.split; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED; import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; Loading @@ -24,19 +24,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. */ Loading Loading @@ -135,4 +143,28 @@ 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_ALL_KINDS_WITH_ALL_PINNED); List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(query, user); ShortcutInfo info = shortcuts != null && shortcuts.size() > 0 ? shortcuts.get(0) : null; return info != null ? info.getActivity() : null; } }
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +81 −21 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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(); Loading @@ -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; Loading Loading @@ -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; Loading @@ -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); } Loading @@ -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; Loading @@ -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() { Loading Loading @@ -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()) { Loading @@ -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) { Loading @@ -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 { Loading @@ -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) { Loading @@ -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 { Loading Loading @@ -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"); Loading Loading @@ -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 { Loading Loading @@ -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(); Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenUtilsTests.kt 0 → 100644 +69 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.wm.shell.common.split import android.content.ComponentName import android.content.pm.LauncherApps import android.content.pm.ShortcutInfo import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.wm.shell.ShellTestCase import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.mock import org.mockito.Mockito.`when` @RunWith(AndroidJUnit4::class) class SplitScreenUtilsTests : ShellTestCase() { @Test fun getShortcutComponent_nullShortcuts() { val launcherApps = mock(LauncherApps::class.java).also { `when`(it.getShortcuts(any(), any())).thenReturn(null) } assertEquals(null, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE, TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps)) } @Test fun getShortcutComponent_noShortcuts() { val launcherApps = mock(LauncherApps::class.java).also { `when`(it.getShortcuts(any(), any())).thenReturn(ArrayList<ShortcutInfo>()) } assertEquals(null, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE, TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps)) } @Test fun getShortcutComponent_validShortcut() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val shortcutInfo = ShortcutInfo.Builder(context, "id").setActivity(component).build() val launcherApps = mock(LauncherApps::class.java).also { `when`(it.getShortcuts(any(), any())).thenReturn(arrayListOf(shortcutInfo)) } assertEquals(component, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE, TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps)) } companion object { val TEST_PACKAGE = "com.android.wm.shell.common.split" val TEST_ACTIVITY = "TestActivity"; val TEST_SHORTCUT_ID = "test_shortcut_1" } } No newline at end of file
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +143 −5 File changed.Preview size limit exceeded, changes collapsed. Show changes