Don't put non-resizeable activities on secondary displays

There is a contract that a non-resizeable activity cannot get
a configuration different from the global config (or fullscreen
config on primary display). This CL ensures that for launching on
secondary displays and checks if target display's config matches
the global config.
If a forced-resizeable activity is launched to a secondary display
or there was an attempt to launch a non-resizeable activity that
failed, corresponding toast message will be displayed.

Bug: 36777179
Test: android.server.cts.ActivityManagerDisplayTests
Test: #testLaunchNonResizeableActivityOnSecondaryDisplay
Test: #testLaunchNonResizeableActivityWithSplitScreen
Test: #testMoveNonResizeableActivityToSecondaryDisplay
Change-Id: I5346afe740e78e4e5ba9a9694e97ac60b92663e9
This commit is contained in:
Andrii Kulian
2017-04-19 10:55:10 -07:00
parent aa6b649a35
commit 036e3adce7
11 changed files with 255 additions and 54 deletions

View File

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

View File

@@ -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 {
}

View File

@@ -1811,10 +1811,14 @@
<string name="report_rejected_touch" translatable="false">Report rejected touch</string>
<!-- Multi-Window strings -->
<!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity and that things might crash/not work properly [CHAR LIMIT=NONE] -->
<!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity to be displayed in split-screen and that things might crash/not work properly [CHAR LIMIT=NONE] -->
<string name="dock_forced_resizable">App may not work with split-screen.</string>
<!-- Warning message when we try to dock a non-resizeble tasks and launch it in fullscreen instead. -->
<!-- Warning message when we try to dock a non-resizeable task and launch it in fullscreen instead. -->
<string name="dock_non_resizeble_failed_to_dock_text">App does not support split-screen.</string>
<!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity to be displayed on a secondary display and that things might crash/not work properly [CHAR LIMIT=NONE] -->
<string name="forced_resizable_secondary_display">App may not work on a secondary display.</string>
<!-- Warning message when we try to launch a non-resizeable activity on a secondary display and launch it on the primary instead. -->
<string name="activity_launch_on_secondary_display_failed_text">App does not support launch on secondary displays.</string>
<!-- accessibility label for button to open settings [CHAR LIMIT=NONE] -->
<string name="accessibility_quick_settings_settings">Open settings.</string>

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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<Integer> mPendingTaskIds = new ArraySet<>();
private final ArraySet<PendingTaskRecord> mPendingTasks = new ArraySet<>();
private final ArraySet<String> 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() {

View File

@@ -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;
@@ -1170,6 +1171,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.

View File

@@ -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 */);
}
}

View File

@@ -981,6 +981,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
@@ -1105,8 +1107,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;
}
@@ -1188,8 +1190,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;
}
@@ -1585,7 +1587,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) {
@@ -2039,16 +2041,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
@@ -2088,7 +2093,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) {
@@ -2154,7 +2159,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:
@@ -2173,8 +2178,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;

View File

@@ -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);

View File

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