summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt1
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/java/android/content/pm/CrossProfileApps.java83
-rw-r--r--core/java/android/content/pm/ICrossProfileApps.aidl2
-rw-r--r--services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java34
-rw-r--r--services/tests/servicestests/Android.bp1
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java92
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),