Added support for maxAspectRatio manifest attribute.
- Allows an app to specify the maximum aspect ratio it supports.
- Support for overriding configuration and bounds at the activity
record and app window token level.
Test: cts/.../run-test CtsAppTestCases android.app.cts.AspectRatioTests
Test: bit FrameworksServicesTests:com.android.server.wm.WindowContainerTests
Test: bit FrameworksServicesTests:com.android.server.wm.WindowFrameTests
Bug: 36505427
Bug: 33205955
Bug: 35810513
Change-Id: Ib2d46ed0c546dd903d09d6bb7162a98bd390ba81
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 6cea483..9a1cd8c 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -47,6 +47,7 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.res.Configuration.EMPTY;
import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.O;
@@ -83,6 +84,7 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Debug;
@@ -130,7 +132,7 @@
/**
* An entry in the history stack, representing an activity.
*/
-final class ActivityRecord implements AppWindowContainerListener {
+final class ActivityRecord extends ConfigurationContainer implements AppWindowContainerListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_AM;
private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
private static final String TAG_SAVED_STATE = TAG + POSTFIX_SAVED_STATE;
@@ -286,11 +288,19 @@
// on the window.
int mRotationAnimationHint = -1;
+ // The bounds of this activity. Mainly used for aspect-ratio compatibility.
+ // TODO(b/36505427): Every level on ConfigurationContainer now has bounds information, which
+ // directly affects the configuration. We should probably move this into that class and have it
+ // handle calculating override configuration from the bounds.
+ private final Rect mBounds = new Rect();
+
/**
* Temp configs used in {@link #ensureActivityConfigurationLocked(int, boolean)}
*/
private final Configuration mTmpConfig1 = new Configuration();
private final Configuration mTmpConfig2 = new Configuration();
+ private final Point mTmpPoint = new Point();
+ private final Rect mTmpBounds = new Rect();
private static String startingWindowStateToString(int state) {
switch (state) {
@@ -344,6 +354,13 @@
pw.println(mLastReportedConfiguration);
pw.print(prefix); pw.print("mLastReportedOverrideConfiguration=");
pw.println(mLastReportedOverrideConfiguration);
+ pw.print(prefix); pw.print("CurrentConfiguration="); pw.println(getConfiguration());
+ if (!getOverrideConfiguration().equals(EMPTY)) {
+ pw.println(prefix + "OverrideConfiguration=" + getOverrideConfiguration());
+ }
+ if (!mBounds.isEmpty()) {
+ pw.println(prefix + "mBounds=" + mBounds);
+ }
if (resultTo != null || resultWho != null) {
pw.print(prefix); pw.print("resultTo="); pw.print(resultTo);
pw.print(" resultWho="); pw.print(resultWho);
@@ -461,10 +478,15 @@
}
if (info != null) {
pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode));
- pw.println(prefix + "supportsPictureInPicture=" + info.supportsPictureInPicture());
+ if (info.supportsPictureInPicture()) {
+ pw.println(prefix + "supportsPictureInPicture=" + info.supportsPictureInPicture());
+ pw.println(prefix + "supportsPictureInPictureWhilePausing: "
+ + supportsPictureInPictureWhilePausing);
+ }
+ if (info.maxAspectRatio != 0) {
+ pw.println(prefix + "maxAspectRatio=" + info.maxAspectRatio);
+ }
}
- pw.println(prefix + "supportsPictureInPictureWhilePausing: "
- + supportsPictureInPictureWhilePausing);
}
private boolean crossesHorizontalSizeThreshold(int firstDp, int secondDp) {
@@ -579,6 +601,22 @@
return task != null && task.getStackId() == FREEFORM_WORKSPACE_STACK_ID;
}
+ @Override
+ protected int getChildCount() {
+ // {@link ActivityRecord} is a leaf node and has no children.
+ return 0;
+ }
+
+ @Override
+ protected ConfigurationContainer getChildAt(int index) {
+ return null;
+ }
+
+ @Override
+ protected ConfigurationContainer getParent() {
+ return task;
+ }
+
static class Token extends IApplicationToken.Stub {
private final WeakReference<ActivityRecord> weakActivity;
@@ -764,15 +802,20 @@
inHistory = true;
- task.updateOverrideConfigurationFromLaunchBounds();
final TaskWindowContainerController taskController = task.getWindowContainerController();
+ // TODO(b/36505427): Maybe this call should be moved inside updateOverrideConfiguration()
+ task.updateOverrideConfigurationFromLaunchBounds();
+ // Make sure override configuration is up-to-date before using to create window controller.
+ updateOverrideConfiguration();
+
mWindowContainerController = new AppWindowContainerController(taskController, appToken,
this, Integer.MAX_VALUE /* add on top */, info.screenOrientation, fullscreen,
(info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, info.configChanges,
task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(),
appInfo.targetSdkVersion, mRotationAnimationHint,
- ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L);
+ ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L,
+ getOverrideConfiguration(), mBounds);
task.addActivityToTop(this);
@@ -1994,8 +2037,73 @@
}
/** Call when override config was sent to the Window Manager to update internal records. */
+ // TODO(b/36505427): Why do we set last reported based on sending the config to WM? Seems like
+ // we should only set this when we actually report to the activity which is what the method
+ // setLastReportedMergedOverrideConfiguration() does. Investigate if this is really needed.
void onOverrideConfigurationSent() {
- mLastReportedOverrideConfiguration.setTo(task.getMergedOverrideConfiguration());
+ mLastReportedOverrideConfiguration.setTo(getMergedOverrideConfiguration());
+ }
+
+ @Override
+ void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+ super.onOverrideConfigurationChanged(overrideConfiguration);
+ if (mWindowContainerController != null) {
+ mWindowContainerController.onOverrideConfigurationChanged(
+ overrideConfiguration, mBounds);
+ // TODO(b/36505427): Can we consolidate the call points of onOverrideConfigurationSent()
+ // to just use this method instead?
+ onOverrideConfigurationSent();
+ }
+ }
+
+ // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
+ private boolean updateOverrideConfiguration() {
+ computeBounds(mTmpBounds);
+ if (mTmpBounds.equals(mBounds)) {
+ return false;
+ }
+ mBounds.set(mTmpBounds);
+ // Bounds changed...update configuration to match.
+ mTmpConfig1.unset();
+ task.computeOverrideConfiguration(mTmpConfig1, mBounds, null /* insetBounds */,
+ false /* overrideWidth */, false /* overrideHeight */);
+ onOverrideConfigurationChanged(mTmpConfig1);
+ return true;
+ }
+
+ /** Computes the override configuration for this activity */
+ // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
+ private void computeBounds(Rect outBounds) {
+ outBounds.setEmpty();
+ final float maxAspectRatio = info.maxAspectRatio;
+ final ActivityStack stack = getStack();
+ if ((task != null && !task.mFullscreen) || maxAspectRatio == 0 || stack == null) {
+ // We don't set override configuration if that activity task isn't fullscreen. I.e. the
+ // activity is in multi-window mode. Or, there isn't a max aspect ratio specified for
+ // the activity.
+ return;
+ }
+
+ stack.getDisplaySize(mTmpPoint);
+ int maxActivityWidth = mTmpPoint.x;
+ int maxActivityHeight = mTmpPoint.y;
+ if (mTmpPoint.x < mTmpPoint.y) {
+ // Width is the shorter side, so we use that to figure-out what the max. height should
+ // be given the aspect ratio.
+ maxActivityHeight = (int) ((maxActivityWidth * maxAspectRatio) + 0.5f);
+ } else {
+ // Height is the shorter side, so we use that to figure-out what the max. width should
+ // be given the aspect ratio.
+ maxActivityWidth = (int) ((maxActivityHeight * maxAspectRatio) + 0.5f);
+ }
+
+ if (mTmpPoint.x <= maxActivityWidth && mTmpPoint.y <= maxActivityHeight) {
+ // The display matches or is less than the activity aspect ratio, so nothing else to do.
+ return;
+ }
+
+ // Compute configuration based on max supported width and height.
+ outBounds.set(0, 0, maxActivityWidth, maxActivityHeight);
}
/**
@@ -2028,13 +2136,16 @@
if (displayChanged) {
mLastReportedDisplayId = newDisplayId;
}
+ // TODO(b/36505427): Is there a better place to do this?
+ updateOverrideConfiguration();
+
// Short circuit: if the two full configurations are equal (the common case), then there is
// nothing to do. We test the full configuration instead of the global and merged override
// configurations because there are cases (like moving a task to the pinned stack) where
// the combine configurations are equal, but would otherwise differ in the override config
mTmpConfig1.setTo(mLastReportedConfiguration);
mTmpConfig1.updateFrom(mLastReportedOverrideConfiguration);
- if (task.getConfiguration().equals(mTmpConfig1) && !forceNewConfig && !displayChanged) {
+ if (getConfiguration().equals(mTmpConfig1) && !forceNewConfig && !displayChanged) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration & display unchanged in " + this);
return true;
@@ -2045,15 +2156,15 @@
// Find changes between last reported merged configuration and the current one. This is used
// to decide whether to relaunch an activity or just report a configuration change.
- final int changes = getTaskConfigurationChanges(mTmpConfig1);
+ final int changes = getConfigurationChanges(mTmpConfig1);
// Update last reported values.
final Configuration newGlobalConfig = service.getGlobalConfiguration();
- final Configuration newTaskMergedOverrideConfig = task.getMergedOverrideConfiguration();
+ final Configuration newMergedOverrideConfig = getMergedOverrideConfiguration();
mTmpConfig1.setTo(mLastReportedConfiguration);
mTmpConfig2.setTo(mLastReportedOverrideConfiguration);
mLastReportedConfiguration.setTo(newGlobalConfig);
- mLastReportedOverrideConfiguration.setTo(newTaskMergedOverrideConfig);
+ mLastReportedOverrideConfiguration.setTo(newMergedOverrideConfig);
if (changes == 0 && !forceNewConfig) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
@@ -2061,9 +2172,9 @@
// There are no significant differences, so we won't relaunch but should still deliver
// the new configuration to the client process.
if (displayChanged) {
- scheduleActivityMovedToDisplay(newDisplayId, newTaskMergedOverrideConfig);
+ scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
} else {
- scheduleConfigurationChanged(newTaskMergedOverrideConfig);
+ scheduleConfigurationChanged(newMergedOverrideConfig);
}
return true;
}
@@ -2088,9 +2199,9 @@
+ Integer.toHexString(changes) + ", handles=0x"
+ Integer.toHexString(info.getRealConfigChanged())
+ ", newGlobalConfig=" + newGlobalConfig
- + ", newTaskMergedOverrideConfig=" + newTaskMergedOverrideConfig);
+ + ", newMergedOverrideConfig=" + newMergedOverrideConfig);
- if (shouldRelaunchLocked(changes, newGlobalConfig, newTaskMergedOverrideConfig)
+ if (shouldRelaunchLocked(changes, newGlobalConfig, newMergedOverrideConfig)
|| forceNewConfig) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
configChangeFlags |= changes;
@@ -2133,13 +2244,13 @@
}
// Default case: the activity can handle this new configuration, so hand it over.
- // NOTE: We only forward the task override configuration as the system level configuration
+ // NOTE: We only forward the override configuration as the system level configuration
// changes is always sent to all processes when they happen so it can just use whatever
// system level configuration it last got.
if (displayChanged) {
- scheduleActivityMovedToDisplay(newDisplayId, newTaskMergedOverrideConfig);
+ scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
} else {
- scheduleConfigurationChanged(newTaskMergedOverrideConfig);
+ scheduleConfigurationChanged(newMergedOverrideConfig);
}
stopFreezingScreenLocked(false);
@@ -2167,31 +2278,31 @@
return (changes&(~configChanged)) != 0;
}
- private int getTaskConfigurationChanges(Configuration lastReportedConfig) {
+ private int getConfigurationChanges(Configuration lastReportedConfig) {
// Determine what has changed. May be nothing, if this is a config that has come back from
// the app after going idle. In that case we just want to leave the official config object
// now in the activity and do nothing else.
- final Configuration currentConfig = task.getConfiguration();
- int taskChanges = lastReportedConfig.diff(currentConfig);
+ final Configuration currentConfig = getConfiguration();
+ int changes = lastReportedConfig.diff(currentConfig);
// We don't want to use size changes if they don't cross boundaries that are important to
// the app.
- if ((taskChanges & CONFIG_SCREEN_SIZE) != 0) {
+ if ((changes & CONFIG_SCREEN_SIZE) != 0) {
final boolean crosses = crossesHorizontalSizeThreshold(lastReportedConfig.screenWidthDp,
currentConfig.screenWidthDp)
|| crossesVerticalSizeThreshold(lastReportedConfig.screenHeightDp,
currentConfig.screenHeightDp);
if (!crosses) {
- taskChanges &= ~CONFIG_SCREEN_SIZE;
+ changes &= ~CONFIG_SCREEN_SIZE;
}
}
- if ((taskChanges & CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
+ if ((changes & CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
final int oldSmallest = lastReportedConfig.smallestScreenWidthDp;
final int newSmallest = currentConfig.smallestScreenWidthDp;
if (!crossesSmallestSizeThreshold(oldSmallest, newSmallest)) {
- taskChanges &= ~CONFIG_SMALLEST_SCREEN_SIZE;
+ changes &= ~CONFIG_SMALLEST_SCREEN_SIZE;
}
}
- return taskChanges;
+ return changes;
}
private static boolean isResizeOnlyChange(int change) {
@@ -2232,7 +2343,7 @@
app.thread.scheduleRelaunchActivity(appToken, pendingResults, pendingNewIntents,
configChangeFlags, !andResume,
new Configuration(service.getGlobalConfiguration()),
- new Configuration(task.getMergedOverrideConfiguration()), preserveWindow);
+ new Configuration(getMergedOverrideConfiguration()), preserveWindow);
// Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
// pass in 'andResume' if this activity is currently resumed, which implies we aren't
// sleeping.
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 97d0aa3..27b8e91 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1380,7 +1380,7 @@
new Configuration(mService.getGlobalConfiguration());
r.setLastReportedGlobalConfiguration(globalConfiguration);
final Configuration mergedOverrideConfiguration =
- new Configuration(task.getMergedOverrideConfiguration());
+ new Configuration(r.getMergedOverrideConfiguration());
r.setLastReportedMergedOverrideConfiguration(mergedOverrideConfiguration);
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
diff --git a/services/core/java/com/android/server/am/ConfigurationContainer.java b/services/core/java/com/android/server/am/ConfigurationContainer.java
index a3e95b8..3d60681 100644
--- a/services/core/java/com/android/server/am/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/am/ConfigurationContainer.java
@@ -22,6 +22,8 @@
* Contains common logic for classes that have override configurations and are organized in a
* hierarchy.
*/
+// TODO(b/36505427): Move to wm package and have WindowContainer use this instead of having its own
+// implementation for merging configuration.
abstract class ConfigurationContainer<E extends ConfigurationContainer> {
/** Contains override configuration settings applied to this configuration container. */
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index a668fea..fd65c10 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -924,12 +924,12 @@
@Override
protected int getChildCount() {
- return 0;
+ return mActivities.size();
}
@Override
protected ConfigurationContainer getChildAt(int index) {
- return null;
+ return mActivities.get(index);
}
@Override
@@ -944,7 +944,7 @@
}
// Close up recents linked list.
- void closeRecentsChain() {
+ private void closeRecentsChain() {
if (mPrevAffiliate != null) {
mPrevAffiliate.setNextAffiliate(mNextAffiliate);
}
@@ -1188,7 +1188,10 @@
throw new IllegalArgumentException("Can not add r=" + " to task=" + this
+ " current parent=" + r.task);
}
+ // TODO(b/36505427): Maybe make task private to ActivityRecord so we can also do
+ // onParentChanged() within the setter?
r.task = this;
+ r.onParentChanged();
// Remove r first, and if it wasn't already in the list and it's fullscreen, count it.
if (!mActivities.remove(r) && r.fullscreen) {
@@ -1995,7 +1998,7 @@
if (mStack == null || StackId.persistTaskBounds(mStack.mStackId)) {
mLastNonFullscreenBounds = mBounds;
}
- calculateOverrideConfig(newConfig, mTmpRect, insetBounds,
+ computeOverrideConfiguration(newConfig, mTmpRect, insetBounds,
mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
}
onOverrideConfigurationChanged(newConfig);
@@ -2008,7 +2011,10 @@
}
/** Clears passed config and fills it with new override values. */
- private void calculateOverrideConfig(Configuration config, Rect bounds, Rect insetBounds,
+ // TODO(b/36505427): TaskRecord.computeOverrideConfiguration() is a utility method that doesn't
+ // depend on task or stacks, but uses those object to get the display to base the calculation
+ // on. Probably best to centralize calculations like this in ConfigurationContainer.
+ void computeOverrideConfiguration(Configuration config, Rect bounds, Rect insetBounds,
boolean overrideWidth, boolean overrideHeight) {
mTmpNonDecorBounds.set(bounds);
mTmpStableBounds.set(bounds);
@@ -2027,7 +2033,7 @@
config.smallestScreenWidthDp =
mService.mStackSupervisor.mDefaultMinSizeOfResizeableTask;
config.screenWidthDp = config.screenHeightDp = config.smallestScreenWidthDp;
- Slog.wtf(TAG, "Expected stack when caclulating override config");
+ Slog.wtf(TAG, "Expected stack when calculating override config");
}
config.orientation = (config.screenWidthDp <= config.screenHeightDp)
@@ -2048,23 +2054,6 @@
}
- /**
- * Using the existing configuration {@param config}, creates a new task override config such
- * that all the fields that are usually set in an override config are set to the ones in
- * {@param config}.
- */
- Configuration extractOverrideConfig(Configuration config) {
- final Configuration extracted = new Configuration();
- extracted.screenWidthDp = config.screenWidthDp;
- extracted.screenHeightDp = config.screenHeightDp;
- extracted.smallestScreenWidthDp = config.smallestScreenWidthDp;
- extracted.orientation = config.orientation;
- // We're only overriding LONG, SIZE and COMPAT parts of screenLayout.
- extracted.screenLayout = config.screenLayout & (Configuration.SCREENLAYOUT_LONG_MASK
- | Configuration.SCREENLAYOUT_SIZE_MASK | Configuration.SCREENLAYOUT_COMPAT_NEEDED);
- return extracted;
- }
-
Rect updateOverrideConfigurationFromLaunchBounds() {
final Rect bounds = validateBounds(getLaunchBounds());
updateOverrideConfiguration(bounds);
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index ef3d87c..e60295d 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -32,9 +32,11 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Trace;
import android.util.Slog;
import android.view.IApplicationToken;
@@ -180,12 +182,13 @@
IApplicationToken token, AppWindowContainerListener listener, int index,
int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
- int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos) {
+ int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
+ Configuration overrideConfig, Rect bounds) {
this(taskController, token, listener, index, requestedOrientation, fullscreen,
showForAllUsers,
configChanges, voiceInteraction, launchTaskBehind, alwaysFocusable,
targetSdkVersion, rotationAnimationHint, inputDispatchingTimeoutNanos,
- WindowManagerService.getInstance());
+ WindowManagerService.getInstance(), overrideConfig, bounds);
}
public AppWindowContainerController(TaskWindowContainerController taskController,
@@ -193,7 +196,7 @@
int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
- WindowManagerService service) {
+ WindowManagerService service, Configuration overrideConfig, Rect bounds) {
super(listener, service);
mHandler = new Handler(service.mH.getLooper());
mToken = token;
@@ -214,7 +217,7 @@
atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
- alwaysFocusable, this);
+ alwaysFocusable, this, overrideConfig, bounds);
if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
+ " controller=" + taskController + " at " + index);
task.addChild(atoken, index);
@@ -226,11 +229,12 @@
boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
- boolean alwaysFocusable, AppWindowContainerController controller) {
+ boolean alwaysFocusable, AppWindowContainerController controller,
+ Configuration overrideConfig, Rect bounds) {
return new AppWindowToken(service, token, voiceInteraction, dc,
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation,
rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
- controller);
+ controller, overrideConfig, bounds);
}
public void removeContainer(int displayId) {
@@ -297,6 +301,17 @@
}
}
+ // TODO(b/36505427): Maybe move to WindowContainerController so other sub-classes can use it as
+ // a generic way to set override config. Need to untangle current ways the override config is
+ // currently set for tasks and displays before we are doing that though.
+ public void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
+ synchronized(mWindowMap) {
+ if (mContainer != null) {
+ mContainer.onOverrideConfigurationChanged(overrideConfiguration, bounds);
+ }
+ }
+ }
+
public void setDisablePreviewScreenshots(boolean disable) {
synchronized (mWindowMap) {
if (mContainer == null) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index d1f1305..72ae90d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -164,6 +164,11 @@
private boolean mLastContainsShowWhenLockedWindow;
private boolean mLastContainsDismissKeyguardWindow;
+ // The bounds of this activity. Mainly used for aspect-ratio compatibility.
+ // TODO(b/36505427): Every level on WindowContainer now has bounds information, which directly
+ // affects the configuration. We should probably move this into that class.
+ private final Rect mBounds = new Rect();
+
ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
@@ -173,8 +178,8 @@
DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
int configChanges, boolean launchTaskBehind, boolean alwaysFocusable,
- AppWindowContainerController controller) {
- this(service, token, voiceInteraction, dc, fullscreen);
+ AppWindowContainerController controller, Configuration overrideConfig, Rect bounds) {
+ this(service, token, voiceInteraction, dc, fullscreen, overrideConfig, bounds);
setController(controller);
mInputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
mShowForAllUsers = showForAllUsers;
@@ -191,7 +196,7 @@
}
AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
- DisplayContent dc, boolean fillsParent) {
+ DisplayContent dc, boolean fillsParent, Configuration overrideConfig, Rect bounds) {
super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true, dc,
false /* ownerCanManageAppTokens */);
appToken = token;
@@ -199,6 +204,30 @@
mFillsParent = fillsParent;
mInputApplicationHandle = new InputApplicationHandle(this);
mAppAnimator = new AppWindowAnimator(this, service);
+ if (overrideConfig != null) {
+ onOverrideConfigurationChanged(overrideConfig);
+ }
+ if (bounds != null) {
+ mBounds.set(bounds);
+ }
+ }
+
+ void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
+ onOverrideConfigurationChanged(overrideConfiguration);
+ if (mBounds.equals(bounds)) {
+ return;
+ }
+ // TODO(b/36505427): If bounds is in WC, then we can automatically call onResize() when set.
+ mBounds.set(bounds);
+ onResize();
+ }
+
+ void getBounds(Rect outBounds) {
+ outBounds.set(mBounds);
+ }
+
+ boolean hasBounds() {
+ return !mBounds.isEmpty();
}
void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e5b00f3..8f391a7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3044,6 +3044,12 @@
mTaskStackContainers.removeExistingAppTokensIfPossible();
}
+ @Override
+ void onDescendantOverrideConfigurationChanged() {
+ setLayoutNeeded();
+ mService.requestTraversal();
+ }
+
static final class TaskForResizePointSearchResult {
boolean searchDone;
Task taskForResize;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 6973c3c..2a02359 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -30,6 +30,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.EMPTY;
/**
* Defines common functionality for classes that can hold windows directly or through their
@@ -315,9 +316,22 @@
void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
mOverrideConfiguration.setTo(overrideConfiguration);
// Update full configuration of this container and all its children.
- onConfigurationChanged(mParent != null ? mParent.getConfiguration() : Configuration.EMPTY);
+ onConfigurationChanged(mParent != null ? mParent.getConfiguration() : EMPTY);
// Update merged override config of this container and all its children.
onMergedOverrideConfigurationChanged();
+
+ if (mParent != null) {
+ mParent.onDescendantOverrideConfigurationChanged();
+ }
+ }
+
+ /**
+ * Notify that a descendant's overrideConfiguration has changed.
+ */
+ void onDescendantOverrideConfigurationChanged() {
+ if (mParent != null) {
+ mParent.onDescendantOverrideConfigurationChanged();
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d4c8b1f..6fd95a4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -717,16 +717,16 @@
mHaveFrame = true;
final Task task = getTask();
- final boolean fullscreenTask = !isInMultiWindowMode();
+ final boolean inFullscreenContainer = inFullscreenContainer();
final boolean windowsAreFloating = task != null && task.isFloating();
final DisplayContent dc = getDisplayContent();
// If the task has temp inset bounds set, we have to make sure all its windows uses
// the temp inset frame. Otherwise different display frames get applied to the main
// window and the child window, making them misaligned.
- if (fullscreenTask) {
+ if (inFullscreenContainer) {
mInsetFrame.setEmpty();
- } else {
+ } else if (task != null && isInMultiWindowMode()) {
task.getTempInsetBounds(mInsetFrame);
}
@@ -740,7 +740,7 @@
// The offset from the layout containing frame to the actual containing frame.
final int layoutXDiff;
final int layoutYDiff;
- if (fullscreenTask || layoutInParentFrame()) {
+ if (inFullscreenContainer || layoutInParentFrame()) {
// We use the parent frame as the containing frame for fullscreen and child windows
mContainingFrame.set(parentFrame);
mDisplayFrame.set(displayFrame);
@@ -749,7 +749,7 @@
layoutXDiff = 0;
layoutYDiff = 0;
} else {
- task.getBounds(mContainingFrame);
+ getContainerBounds(mContainingFrame);
if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
// If the bounds are frozen, we still want to translate the window freely and only
@@ -883,7 +883,7 @@
Math.min(mStableFrame.bottom, mFrame.bottom));
}
- if (fullscreenTask && !windowsAreFloating) {
+ if (inFullscreenContainer && !windowsAreFloating) {
// Windows that are not fullscreen can be positioned outside of the display frame,
// but that is not a reason to provide them with overscan insets.
mOverscanInsets.set(Math.max(mOverscanFrame.left - layoutContainingFrame.left, 0),
@@ -908,10 +908,10 @@
getDisplayContent().getLogicalDisplayRect(mTmpRect);
// Override right and/or bottom insets in case if the frame doesn't fit the screen in
// non-fullscreen mode.
- boolean overrideRightInset = !windowsAreFloating && !fullscreenTask &&
- mFrame.right > mTmpRect.right;
- boolean overrideBottomInset = !windowsAreFloating && !fullscreenTask &&
- mFrame.bottom > mTmpRect.bottom;
+ boolean overrideRightInset = !windowsAreFloating && !inFullscreenContainer
+ && mFrame.right > mTmpRect.right;
+ boolean overrideBottomInset = !windowsAreFloating && !inFullscreenContainer
+ && mFrame.bottom > mTmpRect.bottom;
mContentInsets.set(mContentFrame.left - mFrame.left,
mContentFrame.top - mFrame.top,
overrideRightInset ? mTmpRect.right - mContentFrame.right
@@ -3122,6 +3122,28 @@
return task != null && !task.isFullscreen();
}
+ /** Is this window in a container that takes up the entire screen space? */
+ private boolean inFullscreenContainer() {
+ if (mAppToken == null) {
+ return true;
+ }
+ if (mAppToken.hasBounds()) {
+ return false;
+ }
+ return !isInMultiWindowMode();
+ }
+
+ /** Returns the appropriate bounds to use for computing frames. */
+ private void getContainerBounds(Rect outBounds) {
+ if (isInMultiWindowMode()) {
+ getTask().getBounds(outBounds);
+ } else if (mAppToken != null){
+ mAppToken.getBounds(outBounds);
+ } else {
+ outBounds.setEmpty();
+ }
+ }
+
boolean isDragResizeChanged() {
return mDragResizing != computeDragResizing();
}
@@ -3137,7 +3159,7 @@
/**
* @return Whether we reported a drag resize change to the application or not already.
*/
- boolean isDragResizingChangeReported() {
+ private boolean isDragResizingChangeReported() {
return mDragResizingChangeReported;
}
@@ -3154,7 +3176,7 @@
* Set whether we got resized but drag resizing flag was false.
* @see #isResizedWhileNotDragResizing().
*/
- void setResizedWhileNotDragResizing(boolean resizedWhileNotDragResizing) {
+ private void setResizedWhileNotDragResizing(boolean resizedWhileNotDragResizing) {
mResizedWhileNotDragResizing = resizedWhileNotDragResizing;
mResizedWhileNotDragResizingReported = !resizedWhileNotDragResizing;
}
@@ -3459,17 +3481,17 @@
final int pw = containingFrame.width();
final int ph = containingFrame.height();
final Task task = getTask();
- final boolean nonFullscreenTask = isInMultiWindowMode();
+ final boolean inNonFullscreenContainer = !inFullscreenContainer();
final boolean noLimits = (mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
// We need to fit it to the display if either
- // a) The task is fullscreen, or we don't have a task (we assume fullscreen for the taskless
- // windows)
+ // a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
+ // for the taskless windows)
// b) If it's a secondary app window, we also need to fit it to the display unless
- // FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on screen,
- // but SurfaceViews want to be always at a specific location so we don't fit it to the
- // display.
- final boolean fitToDisplay = (task == null || !nonFullscreenTask)
+ // FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on
+ // screen, but SurfaceViews want to be always at a specific location so we don't fit it to
+ // the display.
+ final boolean fitToDisplay = (task == null || !inNonFullscreenContainer)
|| ((mAttrs.type != TYPE_BASE_APPLICATION) && !noLimits);
float x, y;
int w,h;
@@ -3514,7 +3536,7 @@
y = mAttrs.y;
}
- if (nonFullscreenTask && !layoutInParentFrame()) {
+ if (inNonFullscreenContainer && !layoutInParentFrame()) {
// Make sure window fits in containing frame since it is in a non-fullscreen task as
// required by {@link Gravity#apply} call.
w = Math.min(w, pw);