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

Commit acad138e authored by Varun Shah's avatar Varun Shah
Browse files

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
parent 824874a9
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -89,6 +89,7 @@ package android {
    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 @@ package android.content.pm {
    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[]);
+26 −1
Original line number Diff line number Diff line
@@ -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 @@ public class CrossProfileApps {
                    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();
        }
+1 −1
Original line number Diff line number Diff line
@@ -28,6 +28,6 @@ import android.os.UserHandle;
 */
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
+7 −0
Original line number Diff line number Diff line
@@ -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. -->
+43 −22
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
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 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
            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 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
                            + 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);
        // 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.
            // 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);
        } 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 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
     * 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 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
                            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;
Loading