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