diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 47817a72e962c..f369955cdc31e 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -21,6 +21,11 @@ import android.content.ComponentName;
/** @hide */
oneway interface ITaskStackListener {
+ /** Activity was resized to be displayed in split-screen. */
+ const int FORCED_RESIZEABLE_REASON_SPLIT_SCREEN = 1;
+ /** Activity was resized to be displayed on a secondary display. */
+ const int FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY = 2;
+
/** Called whenever there are changes to the state of tasks in a stack. */
void onTaskStackChanged();
@@ -49,14 +54,25 @@ oneway interface ITaskStackListener {
/**
* Called when we launched an activity that we forced to be resizable.
+ *
+ * @param packageName Package name of the top activity in the task.
+ * @param taskId Id of the task.
+ * @param reason {@link #FORCED_RESIZEABLE_REASON_SPLIT_SCREEN} or
+ * {@link #FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY}.
*/
- void onActivityForcedResizable(String packageName, int taskId);
+ void onActivityForcedResizable(String packageName, int taskId, int reason);
/**
- * Callen when we launched an activity that is dismissed the docked stack.
+ * Called when we launched an activity that dismissed the docked stack.
*/
void onActivityDismissingDockedStack();
+ /**
+ * Called when an activity was requested to be launched on a secondary display but was not
+ * allowed there.
+ */
+ void onActivityLaunchOnSecondaryDisplayFailed();
+
/**
* Called when a task is added.
*
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 57fc874517b79..307fc9128ed22 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -51,13 +51,18 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub {
}
@Override
- public void onActivityForcedResizable(String packageName, int taskId) throws RemoteException {
+ public void onActivityForcedResizable(String packageName, int taskId, int reason)
+ throws RemoteException {
}
@Override
public void onActivityDismissingDockedStack() throws RemoteException {
}
+ @Override
+ public void onActivityLaunchOnSecondaryDisplayFailed() throws RemoteException {
+ }
+
@Override
public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b70597f29a72f..9e88578756940 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1811,10 +1811,14 @@
Report rejected touch
-
+
App may not work with split-screen.
-
+
App does not support split-screen.
+
+ App may not work on a secondary display.
+
+ App does not support launch on secondary displays.
Open settings.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 429ace62ad160..2f122825fe73d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -156,8 +156,9 @@ public class SystemServicesProxy {
public void onPinnedActivityRestartAttempt() { }
public void onPinnedStackAnimationStarted() { }
public void onPinnedStackAnimationEnded() { }
- public void onActivityForcedResizable(String packageName, int taskId) { }
+ public void onActivityForcedResizable(String packageName, int taskId, int reason) { }
public void onActivityDismissingDockedStack() { }
+ public void onActivityLaunchOnSecondaryDisplayFailed() { }
public void onTaskProfileLocked(int taskId, int userId) { }
/**
@@ -224,9 +225,9 @@ public class SystemServicesProxy {
}
@Override
- public void onActivityForcedResizable(String packageName, int taskId)
+ public void onActivityForcedResizable(String packageName, int taskId, int reason)
throws RemoteException {
- mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, 0, packageName)
+ mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
.sendToTarget();
}
@@ -235,6 +236,11 @@ public class SystemServicesProxy {
mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK);
}
+ @Override
+ public void onActivityLaunchOnSecondaryDisplayFailed() throws RemoteException {
+ mHandler.sendEmptyMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED);
+ }
+
@Override
public void onTaskProfileLocked(int taskId, int userId) {
mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
@@ -1235,6 +1241,7 @@ public class SystemServicesProxy {
private static final int ON_TASK_PROFILE_LOCKED = 8;
private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
private static final int ON_ACTIVITY_UNPINNED = 10;
+ private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11;
@Override
public void handleMessage(Message msg) {
@@ -1285,7 +1292,7 @@ public class SystemServicesProxy {
case ON_ACTIVITY_FORCED_RESIZABLE: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
mTaskStackListeners.get(i).onActivityForcedResizable(
- (String) msg.obj, msg.arg1);
+ (String) msg.obj, msg.arg1, msg.arg2);
}
break;
}
@@ -1295,6 +1302,12 @@ public class SystemServicesProxy {
}
break;
}
+ case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onActivityLaunchOnSecondaryDisplayFailed();
+ }
+ break;
+ }
case ON_TASK_PROFILE_LOCKED: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2);
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivity.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivity.java
index 30bf060a12444..4415bd7a631b7 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivity.java
@@ -16,6 +16,9 @@
package com.android.systemui.stackdivider;
+import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
+import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
+
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
@@ -34,6 +37,8 @@ import com.android.systemui.R;
*/
public class ForcedResizableInfoActivity extends Activity implements OnTouchListener {
+ public static final String EXTRA_FORCED_RESIZEABLE_REASON = "extra_forced_resizeable_reason";
+
private static final long DISMISS_DELAY = 2500;
private final Runnable mFinishRunnable = new Runnable() {
@@ -48,8 +53,21 @@ public class ForcedResizableInfoActivity extends Activity implements OnTouchList
super.onCreate(savedInstanceState);
setContentView(R.layout.forced_resizable_activity);
TextView tv = (TextView) findViewById(com.android.internal.R.id.message);
- tv.setText(R.string.dock_forced_resizable);
- getWindow().setTitle(getString(R.string.dock_forced_resizable));
+ int reason = getIntent().getIntExtra(EXTRA_FORCED_RESIZEABLE_REASON, -1);
+ String text;
+ switch (reason) {
+ case FORCED_RESIZEABLE_REASON_SPLIT_SCREEN:
+ text = getString(R.string.dock_forced_resizable);
+ break;
+ case FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY:
+ text = getString(R.string.forced_resizable_secondary_display);
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected forced resizeable reason: "
+ + reason);
+ }
+ tv.setText(text);
+ getWindow().setTitle(text);
getWindow().getDecorView().setOnTouchListener(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
index 17b494e7c68f5..a2c782eb94f88 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -16,6 +16,9 @@
package com.android.systemui.stackdivider;
+import static com.android.systemui.stackdivider.ForcedResizableInfoActivity
+ .EXTRA_FORCED_RESIZEABLE_REASON;
+
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
@@ -43,7 +46,7 @@ public class ForcedResizableInfoActivityController {
private static final int TIMEOUT = 1000;
private final Context mContext;
private final Handler mHandler = new Handler();
- private final ArraySet mPendingTaskIds = new ArraySet<>();
+ private final ArraySet mPendingTasks = new ArraySet<>();
private final ArraySet mPackagesShownInSession = new ArraySet<>();
private boolean mDividerDraging;
@@ -54,20 +57,41 @@ public class ForcedResizableInfoActivityController {
}
};
+ /** Record of force resized task that's pending to be handled. */
+ private class PendingTaskRecord {
+ int taskId;
+ /**
+ * {@link android.app.ITaskStackListener#FORCED_RESIZEABLE_REASON_SPLIT_SCREEN} or
+ * {@link android.app.ITaskStackListener#FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY}
+ */
+ int reason;
+
+ PendingTaskRecord(int taskId, int reason) {
+ this.taskId = taskId;
+ this.reason = reason;
+ }
+ }
+
public ForcedResizableInfoActivityController(Context context) {
mContext = context;
EventBus.getDefault().register(this);
SystemServicesProxy.getInstance(context).registerTaskStackListener(
new TaskStackListener() {
@Override
- public void onActivityForcedResizable(String packageName, int taskId) {
- activityForcedResizable(packageName, taskId);
+ public void onActivityForcedResizable(String packageName, int taskId,
+ int reason) {
+ activityForcedResizable(packageName, taskId, reason);
}
@Override
public void onActivityDismissingDockedStack() {
activityDismissingDockedStack();
}
+
+ @Override
+ public void onActivityLaunchOnSecondaryDisplayFailed() {
+ activityLaunchOnSecondaryDisplayFailed();
+ }
});
}
@@ -93,11 +117,11 @@ public class ForcedResizableInfoActivityController {
showPending();
}
- private void activityForcedResizable(String packageName, int taskId) {
+ private void activityForcedResizable(String packageName, int taskId, int reason) {
if (debounce(packageName)) {
return;
}
- mPendingTaskIds.add(taskId);
+ mPendingTasks.add(new PendingTaskRecord(taskId, reason));
postTimeout();
}
@@ -106,16 +130,23 @@ public class ForcedResizableInfoActivityController {
R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT));
}
+ private void activityLaunchOnSecondaryDisplayFailed() {
+ EventBus.getDefault().send(new ShowUserToastEvent(
+ R.string.activity_launch_on_secondary_display_failed_text, Toast.LENGTH_SHORT));
+ }
+
private void showPending() {
mHandler.removeCallbacks(mTimeoutRunnable);
- for (int i = mPendingTaskIds.size() - 1; i >= 0; i--) {
+ for (int i = mPendingTasks.size() - 1; i >= 0; i--) {
+ PendingTaskRecord pendingRecord = mPendingTasks.valueAt(i);
Intent intent = new Intent(mContext, ForcedResizableInfoActivity.class);
ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchTaskId(mPendingTaskIds.valueAt(i));
+ options.setLaunchTaskId(pendingRecord.taskId);
options.setTaskOverlay(true, false /* canResume */);
+ intent.putExtra(EXTRA_FORCED_RESIZEABLE_REASON, pendingRecord.reason);
mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
}
- mPendingTaskIds.clear();
+ mPendingTasks.clear();
}
private void postTimeout() {
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 43904d66cef42..6e84ed6f3c79f 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -68,6 +68,7 @@ import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SAVED_STATE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SCREENSHOTS;
@@ -1162,6 +1163,17 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
|| service.mForceResizableActivities);
}
+ /**
+ * Check whether this activity can be launched on the specified display.
+ * @param displayId Target display id.
+ * @return {@code true} if either it is the default display or this activity is resizeable and
+ * can be put a secondary screen.
+ */
+ boolean canBeLaunchedOnDisplay(int displayId) {
+ return service.mStackSupervisor.canPlaceEntityOnDisplay(displayId,
+ supportsResizeableMultiWindow());
+ }
+
/**
* @param beforeStopping Whether this check is for an auto-enter-pip operation, that is to say
* the activity has requested to enter PiP when it would otherwise be stopped.
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index ab703409a3934..6010befc11710 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -33,6 +33,8 @@ import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.StackId.LAST_STATIC_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
+import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
+import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -108,6 +110,7 @@ import android.app.ActivityManager.StackInfo;
import android.app.ActivityOptions;
import android.app.AppOpsManager;
import android.app.IActivityContainerCallback;
+import android.app.ITaskStackListener;
import android.app.ProfilerInfo;
import android.app.ResultInfo;
import android.app.StatusBarManager;
@@ -485,6 +488,33 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
activityDisplay.onOverrideConfigurationChanged(overrideConfiguration);
}
+ /** Check if placing task or activity on specified display is allowed. */
+ boolean canPlaceEntityOnDisplay(int displayId, boolean resizeable) {
+ return displayId == DEFAULT_DISPLAY || (mService.mSupportsMultiDisplay
+ && (resizeable || displayConfigMatchesGlobal(displayId)));
+ }
+
+ /**
+ * Check if configuration of specified display matches current global config.
+ * Used to check if we can put a non-resizeable activity on a secondary display and it will get
+ * the same config as on the default display.
+ * @param displayId Id of the display to check.
+ * @return {@code true} if configuration matches.
+ */
+ private boolean displayConfigMatchesGlobal(int displayId) {
+ if (displayId == DEFAULT_DISPLAY) {
+ return true;
+ }
+ if (displayId == INVALID_DISPLAY) {
+ return false;
+ }
+ final ActivityDisplay targetDisplay = mActivityDisplays.get(displayId);
+ if (targetDisplay == null) {
+ throw new IllegalArgumentException("No display found with id: " + displayId);
+ }
+ return getConfiguration().equals(targetDisplay.getConfiguration());
+ }
+
static class FindTaskResult {
ActivityRecord r;
boolean matchedByRootAffinity;
@@ -2104,8 +2134,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
if (DEBUG_STACK) Slog.d(TAG_STACK,
"findTaskToMoveToFront: moved to front of stack=" + currentStack);
- handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, currentStack.mStackId,
- forceNonResizeable);
+ handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, DEFAULT_DISPLAY,
+ currentStack.mStackId, forceNonResizeable);
}
boolean canUseActivityOptionsLaunchBounds(ActivityOptions options, int launchStackId) {
@@ -2156,7 +2186,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// 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)) {
+ if (mService.mActivityStarter.isValidLaunchStackId(stack.mStackId, displayId, r)) {
return stack;
}
}
@@ -2164,7 +2194,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// 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)) {
+ if (mService.mActivityStarter.isValidLaunchStackId(newDynamicStackId, displayId, r)) {
return createStackOnDisplay(newDynamicStackId, displayId, true /*onTop*/);
}
}
@@ -3987,31 +4017,68 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
}
- void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredStackId, int actualStackId) {
- handleNonResizableTaskIfNeeded(task, preferredStackId, actualStackId,
+ void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredStackId,
+ int preferredDisplayId, int actualStackId) {
+ handleNonResizableTaskIfNeeded(task, preferredStackId, preferredDisplayId, actualStackId,
false /* forceNonResizable */);
}
- void handleNonResizableTaskIfNeeded(
- TaskRecord task, int preferredStackId, int actualStackId, boolean forceNonResizable) {
- if ((!isStackDockedInEffect(actualStackId) && preferredStackId != DOCKED_STACK_ID)
- || task.isHomeTask()) {
+ private void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredStackId,
+ int preferredDisplayId, int actualStackId, boolean forceNonResizable) {
+ final boolean isSecondaryDisplayPreferred =
+ (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY)
+ || StackId.isDynamicStack(preferredStackId);
+ if (((!isStackDockedInEffect(actualStackId) && preferredStackId != DOCKED_STACK_ID)
+ && !isSecondaryDisplayPreferred) || task.isHomeTask()) {
return;
}
+ // Handle incorrect launch/move to secondary display if needed.
+ final boolean launchOnSecondaryDisplayFailed;
+ if (isSecondaryDisplayPreferred) {
+ final int actualDisplayId = task.getStack().mDisplayId;
+ if (!task.canBeLaunchedOnDisplay(actualDisplayId)) {
+ // The task landed on an inappropriate display somehow, move it to the default
+ // display.
+ // TODO(multi-display): Find proper stack for the task on the default display.
+ mService.moveTaskToStack(task.taskId, FULLSCREEN_WORKSPACE_STACK_ID,
+ true /* toTop */);
+ launchOnSecondaryDisplayFailed = true;
+ } else {
+ // The task might have landed on a display different from requested.
+ launchOnSecondaryDisplayFailed = actualDisplayId == DEFAULT_DISPLAY
+ || (preferredDisplayId != INVALID_DISPLAY
+ && preferredDisplayId != actualDisplayId);
+ }
+ } else {
+ // The task wasn't requested to be on a secondary display.
+ launchOnSecondaryDisplayFailed = false;
+ }
+
final ActivityRecord topActivity = task.getTopActivity();
- if (!task.supportsSplitScreen() || forceNonResizable) {
- // Display a warning toast that we tried to put a non-dockable task in the docked stack.
- mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack();
+ if (launchOnSecondaryDisplayFailed || !task.supportsSplitScreen() || forceNonResizable) {
+ if (launchOnSecondaryDisplayFailed) {
+ // Display a warning toast that we tried to put a non-resizeable task on a secondary
+ // display with config different from global config.
+ mService.mTaskChangeNotificationController
+ .notifyActivityLaunchOnSecondaryDisplayFailed();
+ } else {
+ // Display a warning toast that we tried to put a non-dockable task in the docked
+ // stack.
+ mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack();
+ }
// Dismiss docked stack. If task appeared to be in docked stack but is not resizable -
// we need to move it to top of fullscreen stack, otherwise it will be covered.
moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, actualStackId == DOCKED_STACK_ID);
} else if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
&& !topActivity.noDisplay) {
- String packageName = topActivity.appInfo.packageName;
+ final String packageName = topActivity.appInfo.packageName;
+ final int reason = isSecondaryDisplayPreferred
+ ? FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY
+ : FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
mService.mTaskChangeNotificationController.notifyActivityForcedResizable(
- task.taskId, packageName);
+ task.taskId, reason, packageName);
}
}
@@ -4084,8 +4151,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
resumeFocusedStackTopActivityLocked();
mWindowManager.executeAppTransition();
} else if (lockTaskModeState != LOCK_TASK_MODE_NONE) {
- handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, task.getStackId(),
- true /* forceNonResizable */);
+ handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, DEFAULT_DISPLAY,
+ task.getStackId(), true /* forceNonResizable */);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 56594d3fdf482..1f4b21b14ed59 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -979,6 +979,8 @@ class ActivityStarter {
final int preferredLaunchStackId =
(mOptions != null) ? mOptions.getLaunchStackId() : INVALID_STACK_ID;
+ final int preferredLaunchDisplayId =
+ (mOptions != null) ? mOptions.getLaunchDisplayId() : DEFAULT_DISPLAY;
if (reusedActivity != null) {
// When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but
@@ -1100,8 +1102,8 @@ class ActivityStarter {
// Don't use mStartActivity.task to show the toast. We're not starting a new activity
// but reusing 'top'. Fields in mStartActivity may not be fully initialized.
- mSupervisor.handleNonResizableTaskIfNeeded(
- top.getTask(), preferredLaunchStackId, topStack.mStackId);
+ mSupervisor.handleNonResizableTaskIfNeeded(top.getTask(), preferredLaunchStackId,
+ preferredLaunchDisplayId, topStack.mStackId);
return START_DELIVERED_TO_TOP;
}
@@ -1183,8 +1185,8 @@ class ActivityStarter {
}
mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack);
- mSupervisor.handleNonResizableTaskIfNeeded(
- mStartActivity.getTask(), preferredLaunchStackId, mTargetStack.mStackId);
+ mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredLaunchStackId,
+ preferredLaunchDisplayId, mTargetStack.mStackId);
return START_SUCCESS;
}
@@ -1580,7 +1582,7 @@ class ActivityStarter {
}
mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTask(), INVALID_STACK_ID,
- mTargetStack.mStackId);
+ DEFAULT_DISPLAY, mTargetStack.mStackId);
// If the caller has requested that the target task be reset, then do so.
if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
@@ -2033,16 +2035,19 @@ class ActivityStarter {
canUseFocusedStack = r.isAssistantActivity();
break;
case DOCKED_STACK_ID:
- // Any activty which supports split screen can go in the docked stack.
+ // Any activity which supports split screen can go in the docked stack.
canUseFocusedStack = r.supportsSplitScreen();
break;
case FREEFORM_WORKSPACE_STACK_ID:
- // Any activty which supports freeform can go in the freeform stack.
+ // Any activity which supports freeform can go in the freeform stack.
canUseFocusedStack = r.supportsFreeform();
break;
default:
- // Dynamic stacks behave similarly to the fullscreen stack and can contain any task.
- canUseFocusedStack = isDynamicStack(focusedStackId);
+ // Dynamic stacks behave similarly to the fullscreen stack and can contain any
+ // resizeable task.
+ // TODO: Check ActivityView after fixing b/35349678.
+ canUseFocusedStack = isDynamicStack(focusedStackId)
+ && r.canBeLaunchedOnDisplay(focusedStack.mDisplayId);
}
return canUseFocusedStack
@@ -2082,7 +2087,7 @@ class ActivityStarter {
"Stack and display id can't be set at the same time.");
}
- if (isValidLaunchStackId(launchStackId, r)) {
+ if (isValidLaunchStackId(launchStackId, launchDisplayId, r)) {
return mSupervisor.getStack(launchStackId, CREATE_IF_NEEDED, ON_TOP);
}
if (launchStackId == DOCKED_STACK_ID) {
@@ -2148,7 +2153,7 @@ class ActivityStarter {
}
}
- boolean isValidLaunchStackId(int stackId, ActivityRecord r) {
+ boolean isValidLaunchStackId(int stackId, int displayId, ActivityRecord r) {
switch (stackId) {
case INVALID_STACK_ID:
case HOME_STACK_ID:
@@ -2167,8 +2172,8 @@ class ActivityStarter {
return r.isAssistantActivity();
default:
// TODO: Check ActivityView after fixing b/35349678.
- if (StackId.isDynamicStack(stackId) && mService.mSupportsMultiDisplay) {
- return true;
+ if (StackId.isDynamicStack(stackId)) {
+ return r.canBeLaunchedOnDisplay(displayId);
}
Slog.e(TAG, "isValidLaunchStackId: Unexpected stackId=" + stackId);
return false;
diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index 94cf092baed3a..7d2bc5b51c2e4 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -48,6 +48,7 @@ class TaskChangeNotificationController {
static final int NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG = 15;
static final int NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG = 16;
static final int NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG = 17;
+ static final int NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED_MSG = 18;
// Delay in notifying task stack change listeners (in millis)
static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -115,13 +116,17 @@ class TaskChangeNotificationController {
};
private final TaskStackConsumer mNotifyActivityForcedResizable = (l, m) -> {
- l.onActivityForcedResizable((String) m.obj, m.arg1);
+ l.onActivityForcedResizable((String) m.obj, m.arg1, m.arg2);
};
private final TaskStackConsumer mNotifyActivityDismissingDockedStack = (l, m) -> {
l.onActivityDismissingDockedStack();
};
+ private final TaskStackConsumer mNotifyActivityLaunchOnSecondaryDisplayFailed = (l, m) -> {
+ l.onActivityLaunchOnSecondaryDisplayFailed();
+ };
+
private final TaskStackConsumer mNotifyTaskProfileLocked = (l, m) -> {
l.onTaskProfileLocked(m.arg1, m.arg2);
};
@@ -191,6 +196,9 @@ class TaskChangeNotificationController {
case NOTIFY_ACTIVITY_DISMISSING_DOCKED_STACK_MSG:
forAllRemoteListeners(mNotifyActivityDismissingDockedStack, msg);
break;
+ case NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED_MSG:
+ forAllRemoteListeners(mNotifyActivityLaunchOnSecondaryDisplayFailed, msg);
+ break;
case NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG:
forAllRemoteListeners(mNotifyTaskProfileLocked, msg);
break;
@@ -324,14 +332,22 @@ class TaskChangeNotificationController {
forAllLocalListeners(mNotifyActivityDismissingDockedStack, message);
}
- void notifyActivityForcedResizable(int taskId, String packageName) {
+ void notifyActivityForcedResizable(int taskId, int reason, String packageName) {
mHandler.removeMessages(NOTIFY_FORCED_RESIZABLE_MSG);
- final Message msg = mHandler.obtainMessage(NOTIFY_FORCED_RESIZABLE_MSG, taskId,
- 0 /* unused */, packageName);
+ final Message msg = mHandler.obtainMessage(NOTIFY_FORCED_RESIZABLE_MSG, taskId, reason,
+ packageName);
forAllLocalListeners(mNotifyActivityForcedResizable, msg);
msg.sendToTarget();
}
+ void notifyActivityLaunchOnSecondaryDisplayFailed() {
+ mHandler.removeMessages(NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED_MSG);
+ final Message msg = mHandler.obtainMessage(
+ NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED_MSG);
+ forAllLocalListeners(mNotifyActivityLaunchOnSecondaryDisplayFailed, msg);
+ msg.sendToTarget();
+ }
+
void notifyTaskCreated(int taskId, ComponentName componentName) {
final Message msg = mHandler.obtainMessage(NOTIFY_TASK_ADDED_LISTENERS_MSG,
taskId, 0 /* unused */, componentName);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index d42b6a7647ce2..dc636e53b18cc 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -97,6 +97,8 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VER
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
@@ -748,7 +750,8 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta
supervisor.resumeFocusedStackTopActivityLocked();
}
- supervisor.handleNonResizableTaskIfNeeded(this, preferredStackId, stackId);
+ // TODO: Handle incorrect request to move before the actual move, not after.
+ supervisor.handleNonResizableTaskIfNeeded(this, preferredStackId, DEFAULT_DISPLAY, stackId);
boolean successful = (preferredStackId == stackId);
if (successful && stackId == DOCKED_STACK_ID) {
@@ -1560,6 +1563,17 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta
&& !ActivityInfo.isPreserveOrientationMode(mResizeMode);
}
+ /**
+ * Check whether this task can be launched on the specified display.
+ * @param displayId Target display id.
+ * @return {@code true} if either it is the default display or this activity is resizeable and
+ * can be put a secondary screen.
+ */
+ boolean canBeLaunchedOnDisplay(int displayId) {
+ return mService.mStackSupervisor.canPlaceEntityOnDisplay(displayId,
+ isResizeable(false /* checkSupportsPip */));
+ }
+
/**
* Check that a given bounds matches the application requested orientation.
*