diff options
| author | 2018-11-28 11:56:08 -0800 | |
|---|---|---|
| committer | 2018-12-07 11:05:13 -0800 | |
| commit | acad138edaf8a8ea517c5bbd7ab0bc6ac8c858a7 (patch) | |
| tree | 0acf3dd65d8c77716af1cb45c9443bdb0bafefc4 | |
| parent | 824874a92ddc599cc239e6961436d364feba6e47 (diff) | |
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
6 files changed, 226 insertions, 31 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index ca06b07a217d..0502ef1e7f55 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -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[]); diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 1c564f3708df..740fd7f963f9 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 @@ 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(); } diff --git a/core/java/android/content/pm/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl index bc2f92a9bf51..d2d66cbda610 100644 --- a/core/java/android/content/pm/ICrossProfileApps.aidl +++ b/core/java/android/content/pm/ICrossProfileApps.aidl @@ -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 diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 3018614fee61..2482b73c92b8 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 65ccecdcafff..7ae2271adb19 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_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); - 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 @@ 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; 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 c4c2ad926954..839b25f8491d 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 @@ public class CrossProfileAppsServiceImplTest { 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 @@ public class CrossProfileAppsServiceImplTest { 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 @@ public class CrossProfileAppsServiceImplTest { 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 @@ public class CrossProfileAppsServiceImplTest { 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 @@ public class CrossProfileAppsServiceImplTest { 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 @@ public class CrossProfileAppsServiceImplTest { 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 @@ public class CrossProfileAppsServiceImplTest { mIApplicationThread, PACKAGE_ONE, ACTIVITY_COMPONENT, - UserHandle.of(PRIMARY_USER)); + UserHandle.of(PRIMARY_USER).getIdentifier(), + true); verify(mActivityTaskManagerInternal) .startActivityAsUser( |