From 10b6509d00dee3d6be73025d4b1075fd2e0a8c64 Mon Sep 17 00:00:00 2001 From: Gareth Vaughan Date: Wed, 19 Jan 2022 19:02:24 -0500 Subject: [PATCH] Start an activity cross profile in same task. Adding new overloaded methods of startMainActivity and startActivity that allow the caller to specify that the new activity should be added to the current task rather than creating a new one. Test: atest android.devicepolicy.cts.CrossProfileAppsTest FrameworksServicesTests:com.android.server.pm.CrossProfileAppsServiceImplTest Bug: 215425075 Change-Id: I21c258c5962daefb32f65779035f4ba8f0474da7 (cherry picked from commit 96ca7241fff35791fb89d26758544268cdbd7a02) Merged-In:I21c258c5962daefb32f65779035f4ba8f0474da7 --- core/api/current.txt | 1 + core/api/system-current.txt | 1 + .../android/content/pm/CrossProfileApps.java | 83 ++++++++++++++++- .../android/content/pm/ICrossProfileApps.aidl | 2 +- .../pm/CrossProfileAppsServiceImpl.java | 34 +++++-- services/tests/servicestests/Android.bp | 1 + .../pm/CrossProfileAppsServiceImplTest.java | 92 ++++++++++++++++--- 7 files changed, 190 insertions(+), 24 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index 27a45aca405b7..d8dd07daa4e9f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -11293,6 +11293,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 e611d6b16bd74..ce64339754416 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3173,6 +3173,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 94f056110bf7a..b6917e2699163 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(); } @@ -179,6 +216,48 @@ public class CrossProfileApps { } } + /** + * 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. + * @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 @@ -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 e2850f111c4f0..4f2c1069275e7 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 b30798485bf7c..89f8be27096ad 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 152f3b3abd135..e3be3a792549f 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 3cb5d5f92810d..ce322f7cb6e6f 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),