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. *