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

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

Merge "Require user id when checking multi-instance support" into main

parents 1cb0abc0 847ee107
Loading
Loading
Loading
Loading
+84 −6
Original line number Diff line number Diff line
@@ -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

/**
@@ -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
@@ -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) {
@@ -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
@@ -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
+6 −2
Original line number Diff line number Diff line
@@ -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());
    }

    //
+5 −1
Original line number Diff line number Diff line
@@ -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
@@ -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 {
@@ -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")
+5 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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);
+10 −6
Original line number Diff line number Diff line
@@ -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()) {
@@ -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 {
@@ -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 {
@@ -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;
@@ -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