diff options
| -rw-r--r-- | api/current.txt | 2 | ||||
| -rw-r--r-- | api/system-current.txt | 2 | ||||
| -rw-r--r-- | api/test-current.txt | 2 | ||||
| -rw-r--r-- | core/java/android/app/ActivityOptions.java | 29 | ||||
| -rw-r--r-- | services/core/java/com/android/server/am/ActivityManagerShellCommand.java | 9 | ||||
| -rw-r--r-- | services/core/java/com/android/server/am/ActivityStackSupervisor.java | 86 | ||||
| -rw-r--r-- | services/core/java/com/android/server/am/ActivityStarter.java | 29 |
7 files changed, 140 insertions, 19 deletions
diff --git a/api/current.txt b/api/current.txt index 298914566676..f43ce6dc8f3f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3892,6 +3892,7 @@ package android.app { public class ActivityOptions { method public android.graphics.Rect getLaunchBounds(); + method public int getLaunchDisplayId(); method public static android.app.ActivityOptions makeBasic(); method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int); method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int); @@ -3902,6 +3903,7 @@ package android.app { method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int); method public void requestUsageTimeReport(android.app.PendingIntent); method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect); + method public android.app.ActivityOptions setLaunchDisplayId(int); method public android.os.Bundle toBundle(); method public void update(android.app.ActivityOptions); field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time"; diff --git a/api/system-current.txt b/api/system-current.txt index ab5acf5dafab..f8132044d2ae 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4024,6 +4024,7 @@ package android.app { public class ActivityOptions { method public android.graphics.Rect getLaunchBounds(); + method public int getLaunchDisplayId(); method public static android.app.ActivityOptions makeBasic(); method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int); method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int); @@ -4034,6 +4035,7 @@ package android.app { method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int); method public void requestUsageTimeReport(android.app.PendingIntent); method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect); + method public android.app.ActivityOptions setLaunchDisplayId(int); method public android.os.Bundle toBundle(); method public void update(android.app.ActivityOptions); field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time"; diff --git a/api/test-current.txt b/api/test-current.txt index 76af8c583608..da025ffff1fe 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -3901,6 +3901,7 @@ package android.app { public class ActivityOptions { method public android.graphics.Rect getLaunchBounds(); + method public int getLaunchDisplayId(); method public static android.app.ActivityOptions makeBasic(); method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int); method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int); @@ -3911,6 +3912,7 @@ package android.app { method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int); method public void requestUsageTimeReport(android.app.PendingIntent); method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect); + method public android.app.ActivityOptions setLaunchDisplayId(int); method public void setLaunchStackId(int); method public android.os.Bundle toBundle(); method public void update(android.app.ActivityOptions); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index d9a46903ee38..1e7f4f04582d 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -18,6 +18,7 @@ package android.app; import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.view.Display.INVALID_DISPLAY; import android.annotation.Nullable; import android.annotation.TestApi; @@ -152,6 +153,12 @@ public class ActivityOptions { private static final String KEY_ANIM_SPECS = "android:activity.animSpecs"; /** + * The display id the activity should be launched into. + * @hide + */ + private static final String KEY_LAUNCH_DISPLAY_ID = "android.activity.launchDisplayId"; + + /** * The stack id the activity should be launched into. * @hide */ @@ -240,6 +247,7 @@ public class ActivityOptions { private int mResultCode; private int mExitCoordinatorIndex; private PendingIntent mUsageTimeReport; + private int mLaunchDisplayId = INVALID_DISPLAY; private int mLaunchStackId = INVALID_STACK_ID; private int mLaunchTaskId = -1; private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; @@ -850,6 +858,7 @@ public class ActivityOptions { mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX); break; } + mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY); mLaunchStackId = opts.getInt(KEY_LAUNCH_STACK_ID, INVALID_STACK_ID); mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1); mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false); @@ -1015,6 +1024,25 @@ public class ActivityOptions { } } + /** + * Gets the id of the display where activity should be launched. + * @return The id of the display where activity should be launched, + * {@link android.view.Display#INVALID_DISPLAY} if not set. + */ + public int getLaunchDisplayId() { + return mLaunchDisplayId; + } + + /** + * Sets the id of the display where activity should be launched. + * @param launchDisplayId The id of the display where the activity should be launched. + * @return {@code this} {@link ActivityOptions} instance. + */ + public ActivityOptions setLaunchDisplayId(int launchDisplayId) { + mLaunchDisplayId = launchDisplayId; + return this; + } + /** @hide */ public int getLaunchStackId() { return mLaunchStackId; @@ -1209,6 +1237,7 @@ public class ActivityOptions { b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex); break; } + b.putInt(KEY_LAUNCH_DISPLAY_ID, mLaunchDisplayId); b.putInt(KEY_LAUNCH_STACK_ID, mLaunchStackId); b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId); b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay); diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 52ad72d819fe..14b843a8f638 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -80,6 +80,7 @@ import static android.app.ActivityManager.RESIZE_MODE_SYSTEM; import static android.app.ActivityManager.RESIZE_MODE_USER; import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.view.Display.INVALID_DISPLAY; final class ActivityManagerShellCommand extends ShellCommand { public static final String NO_CLASS_ERROR_CODE = "Error type 3"; @@ -114,6 +115,7 @@ final class ActivityManagerShellCommand extends ShellCommand { private String mProfileFile; private int mSamplingInterval; private boolean mAutoStop; + private int mDisplayId; private int mStackId; final boolean mDumping; @@ -249,6 +251,7 @@ final class ActivityManagerShellCommand extends ShellCommand { mSamplingInterval = 0; mAutoStop = false; mUserId = defUser; + mDisplayId = INVALID_DISPLAY; mStackId = INVALID_STACK_ID; return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() { @@ -278,6 +281,8 @@ final class ActivityManagerShellCommand extends ShellCommand { mUserId = UserHandle.parseUserArg(getNextArgRequired()); } else if (opt.equals("--receiver-permission")) { mReceiverPermission = getNextArgRequired(); + } else if (opt.equals("--display")) { + mDisplayId = Integer.parseInt(getNextArgRequired()); } else if (opt.equals("--stack")) { mStackId = Integer.parseInt(getNextArgRequired()); } else { @@ -354,6 +359,10 @@ final class ActivityManagerShellCommand extends ShellCommand { int res; final long startTime = SystemClock.uptimeMillis(); ActivityOptions options = null; + if (mDisplayId != INVALID_DISPLAY) { + options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(mDisplayId); + } if (mStackId != INVALID_STACK_ID) { options = ActivityOptions.makeBasic(); options.setLaunchStackId(mStackId); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 282ec5052e4f..1cbb52f11ce2 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -38,6 +38,8 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.FLAG_PRIVATE; +import static android.view.Display.INVALID_DISPLAY; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONTAINERS; @@ -90,6 +92,7 @@ import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED; import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS; import android.Manifest; +import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; @@ -1483,16 +1486,35 @@ public class ActivityStackSupervisor extends ConfigurationContainer Slog.w(TAG, message); return false; } - if (options != null && options.getLaunchTaskId() != -1) { - final int startInTaskPerm = mService.checkPermission(START_TASKS_FROM_RECENTS, - callingPid, callingUid); - if (startInTaskPerm != PERMISSION_GRANTED) { - final String msg = "Permission Denial: starting " + intent.toString() - + " from " + callerApp + " (pid=" + callingPid - + ", uid=" + callingUid + ") with launchTaskId=" - + options.getLaunchTaskId(); - Slog.w(TAG, msg); - throw new SecurityException(msg); + if (options != null) { + if (options.getLaunchTaskId() != INVALID_STACK_ID) { + final int startInTaskPerm = mService.checkPermission(START_TASKS_FROM_RECENTS, + callingPid, callingUid); + if (startInTaskPerm != PERMISSION_GRANTED) { + final String msg = "Permission Denial: starting " + intent.toString() + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ") with launchTaskId=" + + options.getLaunchTaskId(); + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + } + // Check if someone tries to launch an activity on a private display with a different + // owner. + final int launchDisplayId = options.getLaunchDisplayId(); + if (launchDisplayId != INVALID_DISPLAY) { + final ActivityDisplay activityDisplay = mActivityDisplays.get(launchDisplayId); + if (activityDisplay != null + && (activityDisplay.mDisplay.getFlags() & FLAG_PRIVATE) != 0) { + if (activityDisplay.mDisplay.getOwnerUid() != callingUid) { + final String msg = "Permission Denial: starting " + intent.toString() + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ") with launchDisplayId=" + + launchDisplayId; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + } } } @@ -1937,7 +1959,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer } ActivityStack getStack(int stackId, boolean createStaticStackIfNeeded, boolean createOnTop) { - ActivityContainer activityContainer = mActivityContainers.get(stackId); + final ActivityContainer activityContainer = mActivityContainers.get(stackId); if (activityContainer != null) { return activityContainer.mStack; } @@ -1948,6 +1970,40 @@ public class ActivityStackSupervisor extends ConfigurationContainer return createStackOnDisplay(stackId, DEFAULT_DISPLAY, createOnTop); } + /** + * Get a topmost stack on the display, that is a valid launch stack for specified activity. + * If there is no such stack, new dynamic stack can be created. + * @param displayId Target display. + * @param r Activity that should be launched there. + * @return Existing stack if there is a valid one, new dynamic stack if it is valid or null. + */ + ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r) { + final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId); + if (activityDisplay == null) { + throw new IllegalArgumentException( + "Display with displayId=" + displayId + " not found."); + } + + // Return the topmost valid stack on the display. + for (int i = activityDisplay.mStacks.size() - 1; i >= 0; --i) { + final ActivityStack stack = activityDisplay.mStacks.get(i); + if (mService.mActivityStarter.isValidLaunchStackId(stack.mStackId, r)) { + return stack; + } + } + + // If there is no valid stack on the external display - check if new dynamic stack will do. + if (displayId != Display.DEFAULT_DISPLAY) { + final int newDynamicStackId = getNextStackId(); + if (mService.mActivityStarter.isValidLaunchStackId(newDynamicStackId, r)) { + return createStackOnDisplay(newDynamicStackId, displayId, true /*onTop*/); + } + } + + Slog.w(TAG, "getValidLaunchStackOnDisplay: can't launch on displayId " + displayId); + return null; + } + ArrayList<ActivityStack> getStacks() { ArrayList<ActivityStack> allStacks = new ArrayList<>(); for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { @@ -2326,11 +2382,15 @@ public class ActivityStackSupervisor extends ConfigurationContainer * Restores a recent task to a stack * @param task The recent task to be restored. * @param stackId The stack to restore the task to (default launch stack will be used - * if stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}). + * if stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID} + * or is not a static stack). * @return true if the task has been restored successfully. */ private boolean restoreRecentTaskLocked(TaskRecord task, int stackId) { - if (stackId == INVALID_STACK_ID) { + if (!StackId.isStaticStack(stackId)) { + // If stack is not static (or stack id is invalid) - use the default one. + // This means that tasks that were on external displays will be restored on the + // primary display. stackId = task.getLaunchStackId(); } else if (stackId == DOCKED_STACK_ID && !task.canGoInDockedStack()) { // Preferred stack is the docked stack, but the task can't go in the docked stack. diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 691d6b9cd628..c96e74f17be4 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -31,6 +31,7 @@ import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.HOME_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static android.app.ActivityManager.StackId.isStaticStack; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; @@ -50,6 +51,8 @@ import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP; +import static android.view.Display.INVALID_DISPLAY; + import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW; @@ -1952,12 +1955,14 @@ class ActivityStarter { // The fullscreen stack can contain any task regardless of if the task is resizeable // or not. So, we let the task go in the fullscreen task if it is the focus stack. + // Same also applies to dynamic stacks, as they behave similar to fullscreen stack. // If the freeform or docked stack has focus, and the activity to be launched is resizeable, // we can also put it in the focused stack. final int focusedStackId = mSupervisor.mFocusedStack.mStackId; final boolean canUseFocusedStack = focusedStackId == FULLSCREEN_WORKSPACE_STACK_ID || (focusedStackId == DOCKED_STACK_ID && r.canGoInDockedStack()) - || (focusedStackId == FREEFORM_WORKSPACE_STACK_ID && r.isResizeableOrForced()); + || (focusedStackId == FREEFORM_WORKSPACE_STACK_ID && r.isResizeableOrForced()) + || !isStaticStack(focusedStackId); if (canUseFocusedStack && (!newTask || mSupervisor.mFocusedStack.mActivityContainer.isEligibleForNewTasks())) { if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, @@ -1965,7 +1970,7 @@ class ActivityStarter { return mSupervisor.mFocusedStack; } - // We first try to put the task in the first dynamic stack. + // We first try to put the task in the first dynamic stack on home display. final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks; for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) { stack = homeDisplayStacks.get(stackNdx); @@ -1994,16 +1999,29 @@ class ActivityStarter { return mReuseTask.getStack(); } + final int launchDisplayId = + (aOptions != null) ? aOptions.getLaunchDisplayId() : INVALID_DISPLAY; + final int launchStackId = (aOptions != null) ? aOptions.getLaunchStackId() : INVALID_STACK_ID; + if (launchStackId != INVALID_STACK_ID && launchDisplayId != INVALID_DISPLAY) { + throw new IllegalArgumentException( + "Stack and display id can't be set at the same time."); + } + if (isValidLaunchStackId(launchStackId, r)) { return mSupervisor.getStack(launchStackId, CREATE_IF_NEEDED, ON_TOP); - } else if (launchStackId == DOCKED_STACK_ID) { + } + if (launchStackId == DOCKED_STACK_ID) { // The preferred launch stack is the docked stack, but it isn't a valid launch stack // for this activity, so we put the activity in the fullscreen stack. return mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED, ON_TOP); } + if (launchDisplayId != INVALID_DISPLAY) { + // Stack id has higher priority than display id. + return mSupervisor.getValidLaunchStackOnDisplay(launchDisplayId, r); + } if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0) { return null; @@ -2047,9 +2065,8 @@ class ActivityStarter { } } - private boolean isValidLaunchStackId(int stackId, ActivityRecord r) { - if (stackId == INVALID_STACK_ID || stackId == HOME_STACK_ID - || !StackId.isStaticStack(stackId)) { + boolean isValidLaunchStackId(int stackId, ActivityRecord r) { + if (stackId == INVALID_STACK_ID || stackId == HOME_STACK_ID) { return false; } |