Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt +84 −6 Original line number Diff line number Diff line Loading @@ -15,16 +15,21 @@ */ package com.android.wm.shell.common import android.annotation.UserIdInt import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.pm.LauncherApps import android.content.pm.PackageManager import android.content.pm.PackageManager.Property import android.os.UserHandle import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellInit import java.io.PrintWriter import java.util.Arrays /** Loading @@ -35,12 +40,23 @@ class MultiInstanceHelper @JvmOverloads constructor( private val packageManager: PackageManager, private val staticAppsSupportingMultiInstance: Array<String> = context.resources .getStringArray(R.array.config_appsSupportMultiInstancesSplit), private val supportsMultiInstanceProperty: Boolean) { shellInit: ShellInit, private val shellCommandHandler: ShellCommandHandler, private val supportsMultiInstanceProperty: Boolean ) : ShellCommandHandler.ShellCommandActionHandler { init { shellInit.addInitCallback(this::onInit, this) } private fun onInit() { shellCommandHandler.addCommandCallback("multi-instance", this, this) } /** * Returns whether a specific component desires to be launched in multiple instances. */ fun supportsMultiInstanceSplit(componentName: ComponentName?): Boolean { fun supportsMultiInstanceSplit(componentName: ComponentName?, @UserIdInt userId: Int): Boolean { if (componentName == null || componentName.packageName == null) { // TODO(b/262864589): Handle empty component case return false Loading @@ -63,8 +79,9 @@ class MultiInstanceHelper @JvmOverloads constructor( // Check the activity property first try { val activityProp = packageManager.getProperty( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName) val activityProp = packageManager.getPropertyAsUser( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName.packageName, componentName.className, userId) // If the above call doesn't throw a NameNotFoundException, then the activity property // should override the application property value if (activityProp.isBoolean) { Loading @@ -80,8 +97,9 @@ class MultiInstanceHelper @JvmOverloads constructor( // Check the application property otherwise try { val appProp = packageManager.getProperty( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName) val appProp = packageManager.getPropertyAsUser( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, null /* className */, userId) if (appProp.isBoolean) { ProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName) return appProp.boolean Loading @@ -96,6 +114,66 @@ class MultiInstanceHelper @JvmOverloads constructor( return false } override fun onShellCommand(args: Array<out String>?, pw: PrintWriter?): Boolean { if (pw == null || args == null || args.isEmpty()) { return false } when (args[0]) { "list" -> return dumpSupportedApps(pw) } return false } override fun printShellCommandHelp(pw: PrintWriter, prefix: String) { pw.println("${prefix}list") pw.println("$prefix Lists all the packages that support the multiinstance property") } /** * Dumps the static allowlist and list of apps that have the declared property in the manifest. */ private fun dumpSupportedApps(pw: PrintWriter): Boolean { pw.println("Static allow list (for all users):") staticAppsSupportingMultiInstance.forEach { pkg -> pw.println(" $pkg") } // TODO(b/391693747): Dump this per-user once PM allows us to query properties // for non-calling users val apps = packageManager.queryApplicationProperty( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI) val activities = packageManager.queryActivityProperty( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI) val appsWithProperty = (apps + activities) .sortedWith(object : Comparator<Property?> { override fun compare(o1: Property?, o2: Property?): Int { if (o1?.packageName != o2?.packageName) { return o1?.packageName!!.compareTo(o2?.packageName!!) } else { if (o1?.className != null) { return o1.className!!.compareTo(o2?.className!!) } else if (o2?.className != null) { return -o2.className!!.compareTo(o1?.className!!) } return 0 } } }) if (appsWithProperty.isNotEmpty()) { pw.println("Apps (User ${context.userId}):") appsWithProperty.forEach { prop -> if (prop.isBoolean && prop.boolean) { if (prop.className != null) { pw.println(" ${prop.packageName}/${prop.className}") } else { pw.println(" ${prop.packageName}") } } } } return true } companion object { /** Returns the component from a PendingIntent */ @JvmStatic Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +6 −2 Original line number Diff line number Diff line Loading @@ -417,9 +417,13 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static MultiInstanceHelper provideMultiInstanceHelper(Context context) { static MultiInstanceHelper provideMultiInstanceHelper( Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler ) { return new MultiInstanceHelper(context, context.getPackageManager(), Flags.supportsMultiInstanceSystemUi()); shellInit, shellCommandHandler, Flags.supportsMultiInstanceSystemUi()); } // Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +5 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode import android.annotation.UserIdInt import android.app.ActivityManager import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions Loading Loading @@ -2741,6 +2742,7 @@ class DesktopTasksController( // TODO(b/358114479): Move this implementation into a separate class. override fun onUnhandledDrag( launchIntent: PendingIntent, @UserIdInt userId: Int, dragEvent: DragEvent, onFinishCallback: Consumer<Boolean>, ): Boolean { Loading @@ -2749,8 +2751,10 @@ class DesktopTasksController( // Not currently in desktop mode, ignore the drop return false } // TODO: val launchComponent = getComponent(launchIntent) if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent)) { if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent, userId)) { // TODO(b/320797628): Should only return early if there is an existing running task, and // notify the user as well. But for now, just ignore the drop. logV("Dropped intent does not support multi-instance") Loading libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +5 −1 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.PendingIntent; Loading Loading @@ -125,6 +126,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll * drag. */ default boolean onUnhandledDrag(@NonNull PendingIntent launchIntent, @UserIdInt int userId, @NonNull DragEvent dragEvent, @NonNull Consumer<Boolean> onFinishCallback) { return false; Loading Loading @@ -444,8 +446,10 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll return; } // TODO(b/391624027): Consider piping through launch intent user if needed later final int userId = launchIntent.getCreatorUserHandle().getIdentifier(); final boolean handled = notifyListeners( l -> l.onUnhandledDrag(launchIntent, dragEvent, onFinishCallback)); l -> l.onUnhandledDrag(launchIntent, userId, dragEvent, onFinishCallback)); if (!handled) { // Nobody handled this, we still have to notify WM onFinishCallback.accept(false); Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +10 −6 Original line number Diff line number Diff line Loading @@ -649,11 +649,12 @@ public class SplitScreenController implements SplitDragPolicy.Starter, @Nullable Bundle options, UserHandle user) { if (options == null) options = new Bundle(); final ActivityOptions activityOptions = ActivityOptions.fromBundle(options); final int userId = user.getIdentifier(); if (samePackage(packageName, getPackageName(reverseSplitPosition(position), null), user.getIdentifier(), getUserId(reverseSplitPosition(position), null))) { userId, getUserId(reverseSplitPosition(position), null))) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit( getShortcutComponent(packageName, shortcutId, user, mLauncherApps))) { getShortcutComponent(packageName, shortcutId, user, mLauncherApps), userId)) { activityOptions.setApplyMultipleTaskFlagForShortcut(true); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else if (isSplitScreenVisible()) { Loading Loading @@ -687,7 +688,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, final int userId1 = shortcutInfo.getUserId(); final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer); if (samePackage(packageName1, packageName2, userId1, userId2)) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity())) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity(), userId1)) { activityOptions.setApplyMultipleTaskFlagForShortcut(true); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else { Loading Loading @@ -735,7 +737,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer); boolean setSecondIntentMultipleTask = false; if (samePackage(packageName1, packageName2, userId1, userId2)) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent))) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent), userId1)) { setSecondIntentMultipleTask = true; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else { Loading Loading @@ -775,7 +778,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, ? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic(); boolean setSecondIntentMultipleTask = false; if (samePackage(packageName1, packageName2, userId1, userId2)) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1))) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1), userId1)) { fillInIntent1 = new Intent(); fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); setSecondIntentMultipleTask = true; Loading Loading @@ -858,7 +862,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter, return; } if (samePackage(packageName1, packageName2, userId1, userId2)) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(intent))) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(intent), userId1)) { // 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
libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt +84 −6 Original line number Diff line number Diff line Loading @@ -15,16 +15,21 @@ */ package com.android.wm.shell.common import android.annotation.UserIdInt import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.pm.LauncherApps import android.content.pm.PackageManager import android.content.pm.PackageManager.Property import android.os.UserHandle import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellInit import java.io.PrintWriter import java.util.Arrays /** Loading @@ -35,12 +40,23 @@ class MultiInstanceHelper @JvmOverloads constructor( private val packageManager: PackageManager, private val staticAppsSupportingMultiInstance: Array<String> = context.resources .getStringArray(R.array.config_appsSupportMultiInstancesSplit), private val supportsMultiInstanceProperty: Boolean) { shellInit: ShellInit, private val shellCommandHandler: ShellCommandHandler, private val supportsMultiInstanceProperty: Boolean ) : ShellCommandHandler.ShellCommandActionHandler { init { shellInit.addInitCallback(this::onInit, this) } private fun onInit() { shellCommandHandler.addCommandCallback("multi-instance", this, this) } /** * Returns whether a specific component desires to be launched in multiple instances. */ fun supportsMultiInstanceSplit(componentName: ComponentName?): Boolean { fun supportsMultiInstanceSplit(componentName: ComponentName?, @UserIdInt userId: Int): Boolean { if (componentName == null || componentName.packageName == null) { // TODO(b/262864589): Handle empty component case return false Loading @@ -63,8 +79,9 @@ class MultiInstanceHelper @JvmOverloads constructor( // Check the activity property first try { val activityProp = packageManager.getProperty( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName) val activityProp = packageManager.getPropertyAsUser( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName.packageName, componentName.className, userId) // If the above call doesn't throw a NameNotFoundException, then the activity property // should override the application property value if (activityProp.isBoolean) { Loading @@ -80,8 +97,9 @@ class MultiInstanceHelper @JvmOverloads constructor( // Check the application property otherwise try { val appProp = packageManager.getProperty( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName) val appProp = packageManager.getPropertyAsUser( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, null /* className */, userId) if (appProp.isBoolean) { ProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName) return appProp.boolean Loading @@ -96,6 +114,66 @@ class MultiInstanceHelper @JvmOverloads constructor( return false } override fun onShellCommand(args: Array<out String>?, pw: PrintWriter?): Boolean { if (pw == null || args == null || args.isEmpty()) { return false } when (args[0]) { "list" -> return dumpSupportedApps(pw) } return false } override fun printShellCommandHelp(pw: PrintWriter, prefix: String) { pw.println("${prefix}list") pw.println("$prefix Lists all the packages that support the multiinstance property") } /** * Dumps the static allowlist and list of apps that have the declared property in the manifest. */ private fun dumpSupportedApps(pw: PrintWriter): Boolean { pw.println("Static allow list (for all users):") staticAppsSupportingMultiInstance.forEach { pkg -> pw.println(" $pkg") } // TODO(b/391693747): Dump this per-user once PM allows us to query properties // for non-calling users val apps = packageManager.queryApplicationProperty( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI) val activities = packageManager.queryActivityProperty( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI) val appsWithProperty = (apps + activities) .sortedWith(object : Comparator<Property?> { override fun compare(o1: Property?, o2: Property?): Int { if (o1?.packageName != o2?.packageName) { return o1?.packageName!!.compareTo(o2?.packageName!!) } else { if (o1?.className != null) { return o1.className!!.compareTo(o2?.className!!) } else if (o2?.className != null) { return -o2.className!!.compareTo(o1?.className!!) } return 0 } } }) if (appsWithProperty.isNotEmpty()) { pw.println("Apps (User ${context.userId}):") appsWithProperty.forEach { prop -> if (prop.isBoolean && prop.boolean) { if (prop.className != null) { pw.println(" ${prop.packageName}/${prop.className}") } else { pw.println(" ${prop.packageName}") } } } } return true } companion object { /** Returns the component from a PendingIntent */ @JvmStatic Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +6 −2 Original line number Diff line number Diff line Loading @@ -417,9 +417,13 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static MultiInstanceHelper provideMultiInstanceHelper(Context context) { static MultiInstanceHelper provideMultiInstanceHelper( Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler ) { return new MultiInstanceHelper(context, context.getPackageManager(), Flags.supportsMultiInstanceSystemUi()); shellInit, shellCommandHandler, Flags.supportsMultiInstanceSystemUi()); } // Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +5 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode import android.annotation.UserIdInt import android.app.ActivityManager import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions Loading Loading @@ -2741,6 +2742,7 @@ class DesktopTasksController( // TODO(b/358114479): Move this implementation into a separate class. override fun onUnhandledDrag( launchIntent: PendingIntent, @UserIdInt userId: Int, dragEvent: DragEvent, onFinishCallback: Consumer<Boolean>, ): Boolean { Loading @@ -2749,8 +2751,10 @@ class DesktopTasksController( // Not currently in desktop mode, ignore the drop return false } // TODO: val launchComponent = getComponent(launchIntent) if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent)) { if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent, userId)) { // TODO(b/320797628): Should only return early if there is an existing running task, and // notify the user as well. But for now, just ignore the drop. logV("Dropped intent does not support multi-instance") Loading
libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +5 −1 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.PendingIntent; Loading Loading @@ -125,6 +126,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll * drag. */ default boolean onUnhandledDrag(@NonNull PendingIntent launchIntent, @UserIdInt int userId, @NonNull DragEvent dragEvent, @NonNull Consumer<Boolean> onFinishCallback) { return false; Loading Loading @@ -444,8 +446,10 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll return; } // TODO(b/391624027): Consider piping through launch intent user if needed later final int userId = launchIntent.getCreatorUserHandle().getIdentifier(); final boolean handled = notifyListeners( l -> l.onUnhandledDrag(launchIntent, dragEvent, onFinishCallback)); l -> l.onUnhandledDrag(launchIntent, userId, dragEvent, onFinishCallback)); if (!handled) { // Nobody handled this, we still have to notify WM onFinishCallback.accept(false); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +10 −6 Original line number Diff line number Diff line Loading @@ -649,11 +649,12 @@ public class SplitScreenController implements SplitDragPolicy.Starter, @Nullable Bundle options, UserHandle user) { if (options == null) options = new Bundle(); final ActivityOptions activityOptions = ActivityOptions.fromBundle(options); final int userId = user.getIdentifier(); if (samePackage(packageName, getPackageName(reverseSplitPosition(position), null), user.getIdentifier(), getUserId(reverseSplitPosition(position), null))) { userId, getUserId(reverseSplitPosition(position), null))) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit( getShortcutComponent(packageName, shortcutId, user, mLauncherApps))) { getShortcutComponent(packageName, shortcutId, user, mLauncherApps), userId)) { activityOptions.setApplyMultipleTaskFlagForShortcut(true); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else if (isSplitScreenVisible()) { Loading Loading @@ -687,7 +688,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, final int userId1 = shortcutInfo.getUserId(); final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer); if (samePackage(packageName1, packageName2, userId1, userId2)) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity())) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity(), userId1)) { activityOptions.setApplyMultipleTaskFlagForShortcut(true); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else { Loading Loading @@ -735,7 +737,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer); boolean setSecondIntentMultipleTask = false; if (samePackage(packageName1, packageName2, userId1, userId2)) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent))) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent), userId1)) { setSecondIntentMultipleTask = true; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else { Loading Loading @@ -775,7 +778,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, ? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic(); boolean setSecondIntentMultipleTask = false; if (samePackage(packageName1, packageName2, userId1, userId2)) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1))) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1), userId1)) { fillInIntent1 = new Intent(); fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); setSecondIntentMultipleTask = true; Loading Loading @@ -858,7 +862,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter, return; } if (samePackage(packageName1, packageName2, userId1, userId2)) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(intent))) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(intent), userId1)) { // 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