Allow untrusted embedding for EMBED_ANY_APP_IN_UNTRUSTED_MODE

Bug: 289199433
Test: atest TaskFragmentTest
Change-Id: Idb8e13d0e9357960a9ca92a225e4e979222d2e69
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index cdb4aea..95e0e1b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -899,6 +899,9 @@
     <!-- Permission required for Cts test - CtsNotificationTestCases -->
     <uses-permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS" />
 
+    <!-- Permission required for Cts test - CtsWindowManagerJetpackTestCases -->
+    <uses-permission android:name="android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE" />
+
     <!-- Permission required for BinaryTransparencyService shell API and host test -->
     <uses-permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
 
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index b2b547e..11e7bb0 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.Manifest.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE;
 import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
@@ -725,6 +726,9 @@
             // TaskFragment to have bounds outside of the parent bounds.
             return false;
         }
+        if (hasEmbedAnyAppInUntrustedModePermission(mTaskFragmentOrganizerUid)) {
+            return true;
+        }
         return (a.info.flags & FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING)
                 == FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
     }
@@ -796,6 +800,15 @@
     }
 
     /**
+     * Checks if a particular app uid has the {@link EMBED_ANY_APP_IN_UNTRUSTED_MODE} permission.
+     */
+    private static boolean hasEmbedAnyAppInUntrustedModePermission(int uid) {
+        return Flags.untrustedEmbeddingAnyAppPermission()
+                && checkPermission(EMBED_ANY_APP_IN_UNTRUSTED_MODE,
+                PermissionChecker.PID_UNKNOWN, uid) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    /**
      * Checks if all activities in the task fragment are embedded as fully trusted.
      * @see #isFullyTrustedEmbedding(ActivityRecord, int)
      * @param uid   uid of the TaskFragment organizer.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 245b2c5..98ca094 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.Manifest.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE;
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -24,11 +26,14 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -65,12 +70,14 @@
 import androidx.test.filters.MediumTest;
 
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.window.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
 
 import java.util.Collections;
 import java.util.Set;
@@ -592,6 +599,70 @@
     }
 
     @Test
+    public void testIsAllowedToBeEmbeddedInTrustedMode_withManageActivityTasksPermission() {
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setCreateParentTask()
+                .createActivityCount(1)
+                .build();
+        final ActivityRecord activity = taskFragment.getTopMostActivity();
+
+        // Not allow embedding activity if not a trusted host.
+        assertEquals(EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
+                taskFragment.isAllowedToEmbedActivity(activity));
+
+        MockitoSession session =
+                mockitoSession().spyStatic(ActivityTaskManagerService.class).startMocking();
+        try {
+            doReturn(PERMISSION_GRANTED).when(() -> {
+                return ActivityTaskManagerService.checkPermission(
+                        eq(MANAGE_ACTIVITY_TASKS), anyInt(), anyInt());
+            });
+            // With the MANAGE_ACTIVITY_TASKS permission, trusted embedding is always allowed.
+            assertTrue(taskFragment.isAllowedToBeEmbeddedInTrustedMode());
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void testIsAllowedToEmbedActivityInUntrustedMode_withUntrustedEmbeddingAnyAppPermission(
+    ) {
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setCreateParentTask()
+                .createActivityCount(1)
+                .build();
+        final ActivityRecord activity = taskFragment.getTopMostActivity();
+
+        // Not allow embedding activity if not a trusted host.
+        assertEquals(EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
+                taskFragment.isAllowedToEmbedActivity(activity));
+
+        MockitoSession session =
+                mockitoSession()
+                        .spyStatic(ActivityTaskManagerService.class)
+                        .spyStatic(Flags.class)
+                        .startMocking();
+        try {
+            doReturn(PERMISSION_GRANTED).when(() -> {
+                return ActivityTaskManagerService.checkPermission(
+                        eq(EMBED_ANY_APP_IN_UNTRUSTED_MODE), anyInt(), anyInt());
+            });
+            // With the EMBED_ANY_APP_IN_UNTRUSTED_MODE permission, untrusted embedding is always
+            // allowed, but it doesn't always allow trusted embedding.
+            doReturn(true).when(() -> Flags.untrustedEmbeddingAnyAppPermission());
+            assertTrue(taskFragment.isAllowedToEmbedActivityInUntrustedMode(activity));
+            assertFalse(taskFragment.isAllowedToEmbedActivityInTrustedMode(activity));
+
+            // If the flag is disabled, the permission doesn't have effect.
+            doReturn(false).when(() -> Flags.untrustedEmbeddingAnyAppPermission());
+            assertFalse(taskFragment.isAllowedToEmbedActivityInUntrustedMode(activity));
+            assertFalse(taskFragment.isAllowedToEmbedActivityInTrustedMode(activity));
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
     public void testIgnoreRequestedOrientationForActivityEmbeddingSplit() {
         // Setup two activities in ActivityEmbedding split.
         final Task task = createTask(mDisplayContent);