Merge "Add API to launch activities on secondary displays"
diff --git a/api/current.txt b/api/current.txt
index 2989145..f43ce6d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3892,6 +3892,7 @@
 
   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 @@
     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 ab5acf5..f813204 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4024,6 +4024,7 @@
 
   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 @@
     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 76af8c5..da025ff 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3901,6 +3901,7 @@
 
   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 @@
     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 d9a4690..1e7f4f0 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -18,6 +18,7 @@
 
 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 @@
     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 @@
     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 @@
                 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 @@
         }
     }
 
+    /**
+     * 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 @@
                 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 52ad72d..14b843a 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_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 @@
     private String mProfileFile;
     private int mSamplingInterval;
     private boolean mAutoStop;
+    private int mDisplayId;
     private int mStackId;
 
     final boolean mDumping;
@@ -249,6 +251,7 @@
         mSamplingInterval = 0;
         mAutoStop = false;
         mUserId = defUser;
+        mDisplayId = INVALID_DISPLAY;
         mStackId = INVALID_STACK_ID;
 
         return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() {
@@ -278,6 +281,8 @@
                     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 @@
             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 282ec50..1cbb52f 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.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.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 @@
             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 @@
     }
 
     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 @@
         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 @@
      * 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 691d6b9..c96e74f 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.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.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 @@
 
         // 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 @@
             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 @@
             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 @@
         }
     }
 
-    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;
         }