Support non-resizeable activities on secondary displays
When launching a non-resizeable activity to different display,
check if the new configuration should update to activities and
notify systemui to show a toast activity with message "App may
not work on a secondary display".
Bug: 112288258
Bug: 112963441
Test: atest ActivityManagerMultiDisplayTests
Test: atest FrameworksServicesTests:ActivityRecordTests
Change-Id: If86bb8593ecca382074583e6bb998fcba1ee96aa
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 628207c..2199bb7 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -17,18 +17,18 @@
package com.android.server.am;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
import static android.app.ActivityManager.TaskDescription.ATTR_TASKDESCRIPTION_PREFIX;
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
+import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
-import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
+import static android.app.ActivityTaskManager.INVALID_STACK_ID;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
@@ -81,6 +81,8 @@
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 android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
+
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_STATES;
@@ -93,6 +95,13 @@
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityRecordProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.ActivityRecordProto.FRONT_OF_TASK;
+import static com.android.server.am.ActivityRecordProto.IDENTIFIER;
+import static com.android.server.am.ActivityRecordProto.PROC_ID;
+import static com.android.server.am.ActivityRecordProto.STATE;
+import static com.android.server.am.ActivityRecordProto.TRANSLUCENT;
+import static com.android.server.am.ActivityRecordProto.VISIBLE;
import static com.android.server.am.ActivityStack.ActivityState.INITIALIZING;
import static com.android.server.am.ActivityStack.ActivityState.PAUSED;
import static com.android.server.am.ActivityStack.ActivityState.PAUSING;
@@ -110,14 +119,6 @@
import static com.android.server.am.TaskPersister.DEBUG;
import static com.android.server.am.TaskPersister.IMAGE_EXTENSION;
import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
-import static com.android.server.am.ActivityRecordProto.CONFIGURATION_CONTAINER;
-import static com.android.server.am.ActivityRecordProto.FRONT_OF_TASK;
-import static com.android.server.am.ActivityRecordProto.IDENTIFIER;
-import static com.android.server.am.ActivityRecordProto.PROC_ID;
-import static com.android.server.am.ActivityRecordProto.STATE;
-import static com.android.server.am.ActivityRecordProto.TRANSLUCENT;
-import static com.android.server.am.ActivityRecordProto.VISIBLE;
-import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -132,6 +133,7 @@
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.app.ResultInfo;
+import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ClientTransaction;
@@ -143,7 +145,6 @@
import android.app.servertransaction.PipModeChangeItem;
import android.app.servertransaction.ResumeActivityItem;
import android.app.servertransaction.WindowVisibilityItem;
-import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -1279,20 +1280,14 @@
/**
* 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.
+ * @return {@code true} if either it is the default display or this activity can be put on a
+ * secondary screen.
*/
boolean canBeLaunchedOnDisplay(int displayId) {
- final TaskRecord task = getTask();
-
- // The resizeability of an Activity's parent task takes precendence over the ActivityInfo.
- // This allows for a non resizable activity to be launched into a resizeable task.
- final boolean resizeable =
- task != null ? task.isResizeable() : supportsResizeableMultiWindow();
-
- return service.mStackSupervisor.canPlaceEntityOnDisplay(displayId,
- resizeable, launchedFromPid, launchedFromUid, info);
+ return service.mStackSupervisor.canPlaceEntityOnDisplay(displayId, launchedFromPid,
+ launchedFromUid, info);
}
/**
@@ -2495,6 +2490,14 @@
}
}
+ /**
+ * @return {@code true} if this activity was reparented to another display but
+ * {@link #ensureActivityConfiguration} is not called.
+ */
+ boolean shouldUpdateConfigForDisplayChanged() {
+ return mLastReportedDisplayId != getDisplayId();
+ }
+
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow) {
return ensureActivityConfiguration(globalChanges, preserveWindow,
false /* ignoreStopState */);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index aa4e68d..91e677b 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -5375,12 +5375,14 @@
display.positionChildAtTop(this, false /* includingParents */);
}
+ /** NOTE: Should only be called from {@link TaskRecord#reparent}. */
void moveToFrontAndResumeStateIfNeeded(ActivityRecord r, boolean moveToFront, boolean setResume,
boolean setPause, String reason) {
if (!moveToFront) {
return;
}
+ final ActivityState origState = r.getState();
// If the activity owns the last resumed activity, transfer that together,
// so that we don't resume the same activity again in the new stack.
// Apps may depend on onResume()/onPause() being called in pairs.
@@ -5393,9 +5395,14 @@
mPausingActivity = r;
schedulePauseTimeout(r);
}
- // Move the stack in which we are placing the activity to the front. The call will also
- // make sure the activity focus is set.
+ // Move the stack in which we are placing the activity to the front.
moveToFront(reason);
+ // If the original state is resumed, there is no state change to update focused app.
+ // So here makes sure the activity focus is set if it is the top.
+ if (origState == RESUMED && r == mStackSupervisor.getTopResumedActivity()) {
+ // TODO(b/111361570): Support multiple focused apps in WM
+ mService.setResumedActivityUncheckLocked(r, reason);
+ }
}
public int getStackId() {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 310898e..8649e83 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -488,8 +488,8 @@
}
/** Check if placing task or activity on specified display is allowed. */
- boolean canPlaceEntityOnDisplay(int displayId, boolean resizeable, int callingPid,
- int callingUid, ActivityInfo activityInfo) {
+ boolean canPlaceEntityOnDisplay(int displayId, int callingPid, int callingUid,
+ ActivityInfo activityInfo) {
if (displayId == DEFAULT_DISPLAY) {
// No restrictions for the default display.
return true;
@@ -498,10 +498,6 @@
// Can't launch on secondary displays if feature is not supported.
return false;
}
- if (!resizeable && !displayConfigMatchesGlobal(displayId)) {
- // Can't apply wrong configuration to non-resizeable activities.
- return false;
- }
if (!isCallerAllowedToLaunchOnDisplay(callingPid, callingUid, displayId, activityInfo)) {
// Can't place activities to a display that has restricted launch rules.
// In this case the request should be made by explicitly adding target display id and
@@ -4530,11 +4526,14 @@
mService.setTaskWindowingMode(task.taskId,
WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, true /* toTop */);
if (preferredDisplayId != actualDisplayId) {
- // Display a warning toast that we tried to put a non-resizeable task on a secondary
- // display with config different from global config.
+ Slog.w(TAG, "Failed to put " + task + " on display " + preferredDisplayId);
+ // Display a warning toast that we failed to put a task on a secondary display.
mService.getTaskChangeNotificationController()
.notifyActivityLaunchOnSecondaryDisplayFailed();
return;
+ } else if (!forceNonResizable && handleForcedResizableTask(task,
+ FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY)) {
+ return;
}
}
@@ -4554,16 +4553,23 @@
return;
}
+ handleForcedResizableTask(task, FORCED_RESIZEABLE_REASON_SPLIT_SCREEN);
+ }
+
+ /**
+ * @return {@code true} if the top activity of the task is forced to be resizable and the user
+ * was notified about activity being forced resized.
+ */
+ private boolean handleForcedResizableTask(TaskRecord task, int reason) {
final ActivityRecord topActivity = task.getTopActivity();
if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
- && !topActivity.noDisplay) {
+ && !topActivity.noDisplay) {
final String packageName = topActivity.appInfo.packageName;
- final int reason = isSecondaryDisplayPreferred
- ? FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY
- : FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
mService.getTaskChangeNotificationController().notifyActivityForcedResizable(
task.taskId, reason, packageName);
+ return true;
}
+ return false;
}
void activityRelaunchedLocked(IBinder token) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index eb41fe7..c4a6d89 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -52,6 +52,7 @@
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
import static android.view.Display.DEFAULT_DISPLAY;
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;
@@ -944,7 +945,8 @@
auxiliaryResponse == null ? null : auxiliaryResponse.filters);
}
- void postStartActivityProcessing(ActivityRecord r, int result, ActivityStack targetStack) {
+ void postStartActivityProcessing(ActivityRecord r, int result,
+ ActivityStack startedActivityStack) {
if (ActivityManager.isStartResultFatalError(result)) {
return;
}
@@ -956,14 +958,6 @@
// about this, so it waits for the new activity to become visible instead.
mSupervisor.reportWaitingActivityLaunchedIfNeeded(r, result);
- ActivityStack startedActivityStack = null;
- final ActivityStack currentStack = r.getStack();
- if (currentStack != null) {
- startedActivityStack = currentStack;
- } else if (mTargetStack != null) {
- startedActivityStack = targetStack;
- }
-
if (startedActivityStack == null) {
return;
}
@@ -1239,23 +1233,41 @@
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
ActivityRecord[] outActivity) {
int result = START_CANCELED;
+ final ActivityStack startedActivityStack;
try {
mService.mWindowManager.deferSurfaceLayout();
result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
startFlags, doResume, options, inTask, outActivity);
} finally {
- // If we are not able to proceed, disassociate the activity from the task. Leaving an
- // activity in an incomplete state can lead to issues, such as performing operations
- // without a window container.
- final ActivityStack stack = mStartActivity.getStack();
- if (!ActivityManager.isStartResultSuccessful(result) && stack != null) {
- stack.finishActivityLocked(mStartActivity, RESULT_CANCELED,
- null /* intentResultData */, "startActivity", true /* oomAdj */);
+ final ActivityStack currentStack = r.getStack();
+ startedActivityStack = currentStack != null ? currentStack : mTargetStack;
+
+ if (ActivityManager.isStartResultSuccessful(result)) {
+ if (startedActivityStack != null) {
+ // If there is no state change (e.g. a resumed activity is reparented to
+ // top of another display) to trigger a visibility/configuration checking,
+ // we have to update the configuration for changing to different display.
+ final ActivityRecord currentTop =
+ startedActivityStack.topRunningActivityLocked();
+ if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
+ mSupervisor.ensureVisibilityAndConfig(currentTop, currentTop.getDisplayId(),
+ true /* markFrozenIfConfigChanged */, false /* deferResume */);
+ }
+ }
+ } else {
+ // If we are not able to proceed, disassociate the activity from the task.
+ // Leaving an activity in an incomplete state can lead to issues, such as
+ // performing operations without a window container.
+ final ActivityStack stack = mStartActivity.getStack();
+ if (stack != null) {
+ stack.finishActivityLocked(mStartActivity, RESULT_CANCELED,
+ null /* intentResultData */, "startActivity", true /* oomAdj */);
+ }
}
mService.mWindowManager.continueSurfaceLayout();
}
- postStartActivityProcessing(r, result, mTargetStack);
+ postStartActivityProcessing(r, result, startedActivityStack);
return result;
}
diff --git a/services/core/java/com/android/server/am/ActivityTaskManagerService.java b/services/core/java/com/android/server/am/ActivityTaskManagerService.java
index 212844a..6935703d 100644
--- a/services/core/java/com/android/server/am/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityTaskManagerService.java
@@ -4688,11 +4688,7 @@
return mSleeping;
}
- /**
- * Update AMS states when an activity is resumed. This should only be called by
- * {@link ActivityStack#onActivityStateChanged(
- * ActivityRecord, ActivityStack.ActivityState, String)} when an activity is resumed.
- */
+ /** Update AMS states when an activity is resumed. */
void setResumedActivityUncheckLocked(ActivityRecord r, String reason) {
final TaskRecord task = r.getTask();
if (task.isActivityTypeStandard()) {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 7256e23..9b42d65 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1521,14 +1521,14 @@
/**
* 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.
+ * @return {@code true} if either it is the default display or this activity can be put on a
+ * secondary display.
*/
boolean canBeLaunchedOnDisplay(int displayId) {
return mService.mStackSupervisor.canPlaceEntityOnDisplay(displayId,
- isResizeable(false /* checkSupportsPip */), -1 /* don't check PID */,
- -1 /* don't check UID */, null /* activityInfo */);
+ -1 /* don't check PID */, -1 /* don't check UID */, null /* activityInfo */);
}
/**