diff options
7 files changed, 190 insertions, 24 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 45f7cf23fe61..b5493a3fa8c7 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -11300,6 +11300,7 @@ package android.content.pm { method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, "android.permission.INTERACT_ACROSS_USERS"}) public void startActivity(@NonNull android.content.Intent, @NonNull android.os.UserHandle, @Nullable android.app.Activity); method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, "android.permission.INTERACT_ACROSS_USERS"}) public void startActivity(@NonNull android.content.Intent, @NonNull android.os.UserHandle, @Nullable android.app.Activity, @Nullable android.os.Bundle); method public void startMainActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle); + method public void startMainActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle, @Nullable android.app.Activity, @Nullable android.os.Bundle); field public static final String ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED = "android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED"; } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 047c0658f9c9..45082a51883d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3192,6 +3192,7 @@ package android.content.pm { } public class CrossProfileApps { + method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, android.Manifest.permission.START_CROSS_PROFILE_ACTIVITIES}) public void startActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle, @Nullable android.app.Activity, @Nullable android.os.Bundle); method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, android.Manifest.permission.START_CROSS_PROFILE_ACTIVITIES}) public void startActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle); } diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 94f056110bf7..b6917e269916 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -104,7 +104,44 @@ public class CrossProfileApps { mContext.getAttributionTag(), component, targetUser.getIdentifier(), - true); + true, + null, + null); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Starts the specified main activity of the caller package in the specified profile, launching + * in the specified activity. + * + * @param component The ComponentName of the activity to launch, it must be exported and has + * action {@link android.content.Intent#ACTION_MAIN}, category + * {@link android.content.Intent#CATEGORY_LAUNCHER}. Otherwise, SecurityException will + * be thrown. + * @param targetUser The UserHandle of the profile, must be one of the users returned by + * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will + * be thrown. + * @param callingActivity The activity to start the new activity from for the purposes of + * deciding which task the new activity should belong to. If {@code null}, the activity + * will always be started in a new task. + * @param options The activity options or {@code null}. See {@link android.app.ActivityOptions}. + */ + public void startMainActivity(@NonNull ComponentName component, + @NonNull UserHandle targetUser, + @Nullable Activity callingActivity, + @Nullable Bundle options) { + try { + mService.startActivityAsUser( + mContext.getIApplicationThread(), + mContext.getPackageName(), + mContext.getAttributionTag(), + component, + targetUser.getIdentifier(), + true, + callingActivity != null ? callingActivity.getActivityToken() : null, + options); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -191,6 +228,48 @@ public class CrossProfileApps { * @param targetUser The UserHandle of the profile, must be one of the users returned by * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will * be thrown. + * @param callingActivity The activity to start the new activity from for the purposes of + * deciding which task the new activity should belong to. If {@code null}, the activity + * will always be started in a new task. + * @param options The activity options or {@code null}. See {@link android.app.ActivityOptions}. + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.INTERACT_ACROSS_PROFILES, + android.Manifest.permission.START_CROSS_PROFILE_ACTIVITIES}) + public void startActivity( + @NonNull ComponentName component, + @NonNull UserHandle targetUser, + @Nullable Activity callingActivity, + @Nullable Bundle options) { + try { + mService.startActivityAsUser( + mContext.getIApplicationThread(), + mContext.getPackageName(), + mContext.getAttributionTag(), + component, + targetUser.getIdentifier(), + false, + callingActivity != null ? callingActivity.getActivityToken() : null, + options); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Starts the specified activity of the caller package in the specified profile. Unlike + * {@link #startMainActivity}, this can start any activity of the caller package, not just + * the main activity. + * The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} + * or {@link android.Manifest.permission#START_CROSS_PROFILE_ACTIVITIES} + * permission and both the caller and target user profiles must be in the same profile 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 @@ -201,7 +280,7 @@ public class CrossProfileApps { try { mService.startActivityAsUser(mContext.getIApplicationThread(), mContext.getPackageName(), mContext.getAttributionTag(), component, - targetUser.getIdentifier(), false); + targetUser.getIdentifier(), false, null, null); } 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 e2850f111c4f..4f2c1069275e 100644 --- a/core/java/android/content/pm/ICrossProfileApps.aidl +++ b/core/java/android/content/pm/ICrossProfileApps.aidl @@ -29,7 +29,7 @@ import android.os.UserHandle; interface ICrossProfileApps { void startActivityAsUser(in IApplicationThread caller, in String callingPackage, in String callingFeatureId, in ComponentName component, int userId, - boolean launchMainActivity); + boolean launchMainActivity, in IBinder task, in Bundle options); void startActivityAsUserByIntent(in IApplicationThread caller, in String callingPackage, in String callingFeatureId, in Intent intent, int userId, in IBinder callingActivity, in Bundle options); diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java index b30798485bf7..89f8be27096a 100644 --- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java @@ -112,7 +112,9 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { String callingFeatureId, ComponentName component, @UserIdInt int userId, - boolean launchMainActivity) throws RemoteException { + boolean launchMainActivity, + IBinder targetTask, + Bundle options) throws RemoteException { Objects.requireNonNull(callingPackage); Objects.requireNonNull(component); @@ -145,8 +147,12 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { 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); + if (targetTask == null) { + launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + } else { + launchIntent.addFlags(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()); @@ -170,15 +176,20 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { } verifyActivityCanHandleIntentAndExported(launchIntent, component, callingUid, userId); + // Always show the cross profile animation + if (options == null) { + options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(); + } else { + options.putAll(ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle()); + } + launchIntent.setPackage(null); launchIntent.setComponent(component); mInjector.getActivityTaskManagerInternal().startActivityAsUser( caller, callingPackage, callingFeatureId, launchIntent, - /* resultTo= */ null, - Intent.FLAG_ACTIVITY_NEW_TASK, - launchMainActivity - ? ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle() - : null, + targetTask, + /* startFlags= */ 0, + options, userId); } @@ -225,6 +236,13 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { verifyActivityCanHandleIntent(launchIntent, callingUid, userId); + // Always show the cross profile animation + if (options == null) { + options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(); + } else { + options.putAll(ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle()); + } + mInjector.getActivityTaskManagerInternal() .startActivityAsUser( caller, diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 152f3b3abd13..e3be3a792549 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -64,6 +64,7 @@ android_test { "testng", "junit", "platform-compat-test-rules", + "ActivityContext", ], aidl: { 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 3cb5d5f92810..ce322f7cb6e6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java @@ -20,6 +20,7 @@ import static org.testng.Assert.assertThrows; import android.Manifest; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.ActivityOptions; import android.app.AppOpsManager; import android.app.IApplicationThread; import android.app.admin.DevicePolicyManagerInternal; @@ -46,6 +47,7 @@ import android.permission.PermissionManager; import android.platform.test.annotations.Presubmit; import android.util.SparseArray; +import com.android.activitycontext.ActivityContext; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.FunctionalUtils.ThrowingSupplier; import com.android.server.LocalServices; @@ -240,7 +242,9 @@ public class CrossProfileAppsServiceImplTest { FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PRIMARY_USER).getIdentifier(), - true)); + true, + /* targetTask */ null, + /* options */ null)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -265,7 +269,9 @@ public class CrossProfileAppsServiceImplTest { FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PRIMARY_USER).getIdentifier(), - false)); + false, + /* targetTask */ null, + /* options */ null)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -292,7 +298,9 @@ public class CrossProfileAppsServiceImplTest { FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), - true)); + true, + /* targetTask */ null, + /* options */ null)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -319,7 +327,9 @@ public class CrossProfileAppsServiceImplTest { FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), - false)); + false, + /* targetTask */ null, + /* options */ null)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -344,7 +354,9 @@ public class CrossProfileAppsServiceImplTest { FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), - true)); + true, + /* targetTask */ null, + /* options */ null)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -369,7 +381,9 @@ public class CrossProfileAppsServiceImplTest { FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), - false)); + false, + /* targetTask */ null, + /* options */ null)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -396,7 +410,9 @@ public class CrossProfileAppsServiceImplTest { FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), - true)); + true, + /* targetTask */ null, + /* options */ null)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -440,7 +456,9 @@ public class CrossProfileAppsServiceImplTest { FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), - false)); + false, + /* targetTask */ null, + /* options */ null)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -465,7 +483,9 @@ public class CrossProfileAppsServiceImplTest { FEATURE_ID, new ComponentName(PACKAGE_TWO, "test"), UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), - true)); + true, + /* targetTask */ null, + /* options */ null)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -490,7 +510,9 @@ public class CrossProfileAppsServiceImplTest { FEATURE_ID, new ComponentName(PACKAGE_TWO, "test"), UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), - false)); + false, + /* targetTask */ null, + /* options */ null)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -515,7 +537,9 @@ public class CrossProfileAppsServiceImplTest { FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(SECONDARY_USER).getIdentifier(), - true)); + true, + /* targetTask */ null, + /* options */ null)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -540,7 +564,9 @@ public class CrossProfileAppsServiceImplTest { FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(SECONDARY_USER).getIdentifier(), - false)); + false, + /* targetTask */ null, + /* options */ null)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -564,7 +590,9 @@ public class CrossProfileAppsServiceImplTest { FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PRIMARY_USER).getIdentifier(), - true); + true, + /* targetTask */ null, + /* options */ null); verify(mActivityTaskManagerInternal) .startActivityAsUser( @@ -578,6 +606,44 @@ public class CrossProfileAppsServiceImplTest { eq(PRIMARY_USER)); } + @Test + public void startActivityAsUser_sameTask_fromProfile_success() throws Exception { + mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER); + + Bundle options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(); + IBinder result = ActivityContext.getWithContext(activity -> { + try { + IBinder targetTask = activity.getActivityToken(); + mCrossProfileAppsServiceImpl.startActivityAsUser( + mIApplicationThread, + PACKAGE_ONE, + FEATURE_ID, + ACTIVITY_COMPONENT, + UserHandle.of(PRIMARY_USER).getIdentifier(), + true, + targetTask, + options); + return targetTask; + } catch (Exception re) { + return null; + } + }); + if (result == null) { + throw new Exception(); + } + + verify(mActivityTaskManagerInternal) + .startActivityAsUser( + nullable(IApplicationThread.class), + eq(PACKAGE_ONE), + eq(FEATURE_ID), + any(Intent.class), + eq(result), + anyInt(), + eq(options), + eq(PRIMARY_USER)); + } + private void mockAppsInstalled(String packageName, int user, boolean installed) { when(mPackageManagerInternal.getPackageInfo( eq(packageName), |