Privileged apps can now launch their activities across profiles.

Introduced a new INTERACT_ACROSS_PROFILES privileged permission which
allows an application to start a managed profile activity from its personal
profile activity.

Added CrossProfileApps#startAnyActivity(ComponentName, UserHandle) which
requires the INTERACT_ACROSS_PROFILES permission and enables an app from
a personal profile to launch an activity within its managed profile app.

Bug: 118186373
Test: atest com.android.server.pm.CrossProfileAppsServiceImplTest
Test: atest cts/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
Change-Id: I28aa05c7e54f60eb6144275d31eaf8813e2f10ad
diff --git a/api/system-current.txt b/api/system-current.txt
index ca06b07..0502ef1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -89,6 +89,7 @@
     field public static final java.lang.String INSTALL_PACKAGE_UPDATES = "android.permission.INSTALL_PACKAGE_UPDATES";
     field public static final java.lang.String INSTALL_SELF_UPDATES = "android.permission.INSTALL_SELF_UPDATES";
     field public static final java.lang.String INTENT_FILTER_VERIFICATION_AGENT = "android.permission.INTENT_FILTER_VERIFICATION_AGENT";
+    field public static final java.lang.String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES";
     field public static final java.lang.String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
     field public static final java.lang.String INTERACT_ACROSS_USERS_FULL = "android.permission.INTERACT_ACROSS_USERS_FULL";
     field public static final java.lang.String INTERNAL_SYSTEM_WINDOW = "android.permission.INTERNAL_SYSTEM_WINDOW";
@@ -1128,6 +1129,10 @@
     field public int targetSandboxVersion;
   }
 
+  public class CrossProfileApps {
+    method public void startAnyActivity(android.content.ComponentName, android.os.UserHandle);
+  }
+
   public final class InstantAppInfo implements android.os.Parcelable {
     ctor public InstantAppInfo(android.content.pm.ApplicationInfo, java.lang.String[], java.lang.String[]);
     ctor public InstantAppInfo(java.lang.String, java.lang.CharSequence, java.lang.String[], java.lang.String[]);
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 1c564f3..740fd7f 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -16,6 +16,8 @@
 package android.content.pm;
 
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
@@ -66,7 +68,30 @@
                     mContext.getIApplicationThread(),
                     mContext.getPackageName(),
                     component,
-                    targetUser);
+                    targetUser.getIdentifier(),
+                    true);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Starts the specified activity of the caller package in the specified profile if the caller
+     * has {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} permission and
+     * both the caller and target user profiles are in the same user group.
+     *
+     * @param component The ComponentName of the activity to launch. It must be exported.
+     * @param targetUser The UserHandle of the profile, must be one of the users returned by
+     *        {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
+     *        be thrown.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)
+    public void startAnyActivity(@NonNull ComponentName component, @NonNull UserHandle targetUser) {
+        try {
+            mService.startActivityAsUser(mContext.getIApplicationThread(),
+                    mContext.getPackageName(), component, targetUser.getIdentifier(), false);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/pm/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl
index bc2f92a..d2d66cb 100644
--- a/core/java/android/content/pm/ICrossProfileApps.aidl
+++ b/core/java/android/content/pm/ICrossProfileApps.aidl
@@ -28,6 +28,6 @@
  */
 interface ICrossProfileApps {
     void startActivityAsUser(in IApplicationThread caller, in String callingPackage,
-            in ComponentName component, in UserHandle user);
+            in ComponentName component, int userId, boolean launchMainActivity);
     List<UserHandle> getTargetUserProfiles(in String callingPackage);
 }
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3018614..2482b73 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2171,6 +2171,13 @@
     <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
         android:protectionLevel="signature|installer" />
 
+    <!-- @SystemApi Allows an application to start an activity within its managed profile from
+         the personal profile.
+         This permission is not available to third party applications.
+         @hide -->
+    <permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi @hide Allows an application to call APIs that allow it to query and manage
          users on the device. This permission is not available to
          third party applications. -->
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index 65ccecd..7ae2271 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -19,6 +19,7 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions;
 import android.app.AppOpsManager;
@@ -77,18 +78,20 @@
             IApplicationThread caller,
             String callingPackage,
             ComponentName component,
-            UserHandle user) throws RemoteException {
+            @UserIdInt int userId,
+            boolean launchMainActivity) throws RemoteException {
         Preconditions.checkNotNull(callingPackage);
         Preconditions.checkNotNull(component);
-        Preconditions.checkNotNull(user);
 
         verifyCallingPackage(callingPackage);
 
+        final int callerUserId = mInjector.getCallingUserId();
+        final int callingUid = mInjector.getCallingUid();
+
         List<UserHandle> allowedTargetUsers = getTargetUserProfilesUnchecked(
-                callingPackage, mInjector.getCallingUserId());
-        if (!allowedTargetUsers.contains(user)) {
-            throw new SecurityException(
-                    callingPackage + " cannot access unrelated user " + user.getIdentifier());
+                callingPackage, callerUserId);
+        if (!allowedTargetUsers.contains(UserHandle.of(userId))) {
+            throw new SecurityException(callingPackage + " cannot access unrelated user " + userId);
         }
 
         // Verify that caller package is starting activity in its own package.
@@ -98,25 +101,43 @@
                             + component.getPackageName());
         }
 
-        final int callingUid = mInjector.getCallingUid();
-
-        // Verify that target activity does handle the intent with ACTION_MAIN and
-        // CATEGORY_LAUNCHER as calling startActivityAsUser ignore them if component is present.
-        final Intent launchIntent = new Intent(Intent.ACTION_MAIN);
-        launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-        // Only package name is set here, as opposed to component name, because intent action and
-        // category are ignored if component name is present while we are resolving intent.
-        launchIntent.setPackage(component.getPackageName());
-        verifyActivityCanHandleIntentAndExported(launchIntent, component, callingUid, user);
+        // Verify that target activity does handle the intent correctly.
+        final Intent launchIntent = new Intent();
+        if (launchMainActivity) {
+            launchIntent.setAction(Intent.ACTION_MAIN);
+            launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+            launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+            // Only package name is set here, as opposed to component name, because intent action
+            // and category are ignored if component name is present while we are resolving intent.
+            launchIntent.setPackage(component.getPackageName());
+        } else {
+            // If the main activity is not being launched and the users are different, the caller
+            // must have the required permission and the users must be in the same profile group
+            // in order to launch any of its own activities.
+            if (callerUserId != userId) {
+                final int permissionFlag = ActivityManager.checkComponentPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_PROFILES, callingUid,
+                        -1, true);
+                if (permissionFlag != PackageManager.PERMISSION_GRANTED
+                        || !mInjector.getUserManager().isSameProfileGroup(callerUserId, userId)) {
+                    throw new SecurityException("Attempt to launch activity without required "
+                            + android.Manifest.permission.INTERACT_ACROSS_PROFILES + " permission"
+                            + " or target user is not in the same profile group.");
+                }
+            }
+            launchIntent.setComponent(component);
+        }
+        verifyActivityCanHandleIntentAndExported(launchIntent, component, callingUid, userId);
 
         launchIntent.setPackage(null);
         launchIntent.setComponent(component);
         mInjector.getActivityTaskManagerInternal().startActivityAsUser(
                 caller, callingPackage, launchIntent,
-                ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(),
-                user.getIdentifier());
+                launchMainActivity
+                        ? ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle()
+                        : null,
+                userId);
     }
 
     private List<UserHandle> getTargetUserProfilesUnchecked(
@@ -163,7 +184,7 @@
      * activity is exported.
      */
     private void verifyActivityCanHandleIntentAndExported(
-            Intent launchIntent, ComponentName component, int callingUid, UserHandle user) {
+            Intent launchIntent, ComponentName component, int callingUid, @UserIdInt int userId) {
         final long ident = mInjector.clearCallingIdentity();
         try {
             final List<ResolveInfo> apps =
@@ -171,7 +192,7 @@
                             launchIntent,
                             MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
                             callingUid,
-                            user.getIdentifier());
+                            userId);
             final int size = apps.size();
             for (int i = 0; i < size; ++i) {
                 final ActivityInfo activityInfo = apps.get(i).activityInfo;
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index c4c2ad9..839b25f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -212,7 +212,29 @@
                                 mIApplicationThread,
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                UserHandle.of(PRIMARY_USER)));
+                                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                                true));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        any(Intent.class),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_currentUser() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                                false));
 
         verify(mActivityTaskManagerInternal, never())
                 .startActivityAsUser(
@@ -234,7 +256,31 @@
                                 mIApplicationThread,
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                UserHandle.of(PROFILE_OF_PRIMARY_USER)));
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        any(Intent.class),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_notInstalled() {
+        mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false));
 
         verify(mActivityTaskManagerInternal, never())
                 .startActivityAsUser(
@@ -254,7 +300,29 @@
                                 mIApplicationThread,
                                 PACKAGE_TWO,
                                 ACTIVITY_COMPONENT,
-                                UserHandle.of(PROFILE_OF_PRIMARY_USER)));
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        any(Intent.class),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_fakeCaller() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_TWO,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false));
 
         verify(mActivityTaskManagerInternal, never())
                 .startActivityAsUser(
@@ -276,7 +344,31 @@
                                 mIApplicationThread,
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                UserHandle.of(PROFILE_OF_PRIMARY_USER)));
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        any(Intent.class),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_notExported() {
+        mActivityInfo.exported = false;
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false));
 
         verify(mActivityTaskManagerInternal, never())
                 .startActivityAsUser(
@@ -296,7 +388,29 @@
                                 mIApplicationThread,
                                 PACKAGE_ONE,
                                 new ComponentName(PACKAGE_TWO, "test"),
-                                UserHandle.of(PROFILE_OF_PRIMARY_USER)));
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        any(Intent.class),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_anotherPackage() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                new ComponentName(PACKAGE_TWO, "test"),
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false));
 
         verify(mActivityTaskManagerInternal, never())
                 .startActivityAsUser(
@@ -316,7 +430,29 @@
                                 mIApplicationThread,
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                UserHandle.of(SECONDARY_USER)));
+                                UserHandle.of(SECONDARY_USER).getIdentifier(),
+                                true));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        any(Intent.class),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_secondaryUser() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(SECONDARY_USER).getIdentifier(),
+                                false));
 
         verify(mActivityTaskManagerInternal, never())
                 .startActivityAsUser(
@@ -335,7 +471,8 @@
                 mIApplicationThread,
                 PACKAGE_ONE,
                 ACTIVITY_COMPONENT,
-                UserHandle.of(PRIMARY_USER));
+                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                true);
 
         verify(mActivityTaskManagerInternal)
                 .startActivityAsUser(