diff --git a/api/test-current.txt b/api/test-current.txt
index 866b9383d40b0..963ef7c540c7b 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -553,11 +553,13 @@ package android.app {
method public int getActivityType();
method public android.graphics.Rect getAppBounds();
method public android.graphics.Rect getBounds();
+ method @NonNull public android.graphics.Rect getMaxBounds();
method public int getRotation();
method public int getWindowingMode();
method public void setActivityType(int);
method public void setAppBounds(android.graphics.Rect);
method public void setBounds(android.graphics.Rect);
+ method public void setMaxBounds(@Nullable android.graphics.Rect);
method public void setRotation(int);
method public void setTo(android.app.WindowConfiguration);
method public void setWindowingMode(int);
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index ec81ae3bc7c2b..79f05a3caa93f 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -310,7 +310,6 @@ public class WindowConfiguration implements Parcelable, Comparable
* The real size may be smaller than the physical size of the screen when the
* window manager is emulating a smaller display (using adb shell wm size).
- *
+ *
+ * In general, {@link #getRealSize(Point)} and {@link WindowManager#getMaximumWindowMetrics()}
+ * report the same bounds except that certain areas of the display may not be available to
+ * windows created in the {@link WindowManager}'s {@link Context}.
+ *
+ * For example, imagine a device which has a multi-task mode that limits windows to half of the
+ * screen. In this case, {@link WindowManager#getMaximumWindowMetrics()} reports the
+ * bounds of the screen half where the window is located, while {@link #getRealSize(Point)}
+ * still reports the bounds of the whole display.
*
* @param outSize Set to the real size of the display.
+ *
+ * @see WindowManager#getMaximumWindowMetrics()
*/
public void getRealSize(Point outSize) {
synchronized (this) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 1d54d9251abb3..cf4315fc7c007 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -72,6 +72,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.IBinder;
@@ -473,9 +474,18 @@ public interface WindowManager extends ViewManager {
*
* Note that this might still be smaller than the size of the physical display if certain areas
* of the display are not available to windows created in this {@link Context}.
+ *
+ * For example, given that there's a device which have a multi-task mode to limit activities
+ * to a half screen. In this case, {@link #getMaximumWindowMetrics()} reports the bounds of
+ * the half screen which the activity is located, while {@link Display#getRealSize(Point)} still
+ * reports the bounds of the whole physical display.
*
- * @see #getMaximumWindowMetrics()
+ * Despite this, {@link #getMaximumWindowMetrics()} and {@link Display#getRealSize(Point)}
+ * reports the same bounds in general.
+ *
+ * @see #getCurrentWindowMetrics()
* @see WindowMetrics
+ * @see Display#getRealSize(Point)
*/
default @NonNull WindowMetrics getMaximumWindowMetrics() {
throw new UnsupportedOperationException();
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 2fe7c021bb21a..b4561c5795b99 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -29,7 +29,6 @@ import android.app.ResourcesManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Insets;
-import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
@@ -233,17 +232,16 @@ public final class WindowManagerImpl implements WindowManager {
@Override
public WindowMetrics getMaximumWindowMetrics() {
- final Rect maxBounds = getMaximumBounds();
+ final Context context = mParentWindow != null ? mParentWindow.getContext() : mContext;
+ final Rect maxBounds = getMaximumBounds(context);
+
return new WindowMetrics(maxBounds, computeWindowInsets(maxBounds));
}
- private Rect getMaximumBounds() {
- // TODO(b/128338354): Current maximum bound is display size, but it should be displayArea
- // bound after displayArea feature is finished.
- final Display display = mContext.getDisplayNoVerify();
- final Point displaySize = new Point();
- display.getRealSize(displaySize);
- return new Rect(0, 0, displaySize.x, displaySize.y);
+ private static Rect getMaximumBounds(Context context) {
+ synchronized (ResourcesManager.getInstance()) {
+ return context.getResources().getConfiguration().windowConfiguration.getMaxBounds();
+ }
}
// TODO(b/150095967): Set window type to LayoutParams
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index c216638117561..b85cbef5329ec 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -204,6 +204,7 @@ message DisplayAreaProto {
optional WindowContainerProto window_container = 1;
optional string name = 2 [ (.android.privacy).dest = DEST_EXPLICIT ];
repeated DisplayAreaChildProto children = 3 [deprecated=true];
+ optional bool is_task_display_area = 4;
}
/* represents a generic child of a DisplayArea */
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 0d8e882ad58fc..13033a6fcf814 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -93,9 +93,17 @@ public abstract class ConfigurationContainer {
private final Rect mTmpRect = new Rect();
static final int BOUNDS_CHANGE_NONE = 0;
- // Return value from {@link setBounds} indicating the position of the override bounds changed.
+
+ /**
+ * Return value from {@link #setBounds(Rect)} indicating the position of the override bounds
+ * changed.
+ */
static final int BOUNDS_CHANGE_POSITION = 1;
- // Return value from {@link setBounds} indicating the size of the override bounds changed.
+
+ /**
+ * Return value from {@link #setBounds(Rect)} indicating the size of the override bounds
+ * changed.
+ */
static final int BOUNDS_CHANGE_SIZE = 1 << 1;
/**
@@ -226,6 +234,11 @@ public abstract class ConfigurationContainer {
return equivalentBounds(getRequestedOverrideBounds(), bounds);
}
+ /** Similar to {@link #equivalentRequestedOverrideBounds(Rect)}, but compares max bounds. */
+ public boolean equivalentRequestedOverrideMaxBounds(Rect bounds) {
+ return equivalentBounds(getRequestedOverrideMaxBounds(), bounds);
+ }
+
/**
* Returns whether the two bounds are equal to each other or are a combination of null or empty.
*/
@@ -238,7 +251,6 @@ public abstract class ConfigurationContainer {
/**
* Returns the effective bounds of this container, inheriting the first non-empty bounds set in
* its ancestral hierarchy, including itself.
- * @return
*/
public Rect getBounds() {
mReturnBounds.set(getConfiguration().windowConfiguration.getBounds());
@@ -249,6 +261,12 @@ public abstract class ConfigurationContainer {
outBounds.set(getBounds());
}
+ /** Similar to {@link #getBounds()}, but reports the max bounds. */
+ public Rect getMaxBounds() {
+ mReturnBounds.set(getConfiguration().windowConfiguration.getMaxBounds());
+ return mReturnBounds;
+ }
+
/**
* Sets {@code out} to the top-left corner of the bounds as returned by {@link #getBounds()}.
*/
@@ -273,6 +291,13 @@ public abstract class ConfigurationContainer {
return mReturnBounds;
}
+ /** Similar to {@link #getRequestedOverrideBounds()}, but returns the max bounds. */
+ public Rect getRequestedOverrideMaxBounds() {
+ mReturnBounds.set(getRequestedOverrideConfiguration().windowConfiguration.getMaxBounds());
+
+ return mReturnBounds;
+ }
+
/**
* Returns {@code true} if the {@link WindowConfiguration} in the requested override
* {@link Configuration} specifies bounds.
@@ -283,7 +308,7 @@ public abstract class ConfigurationContainer {
/**
* Sets the passed in {@link Rect} to the current bounds.
- * @see {@link #getRequestedOverrideBounds()}.
+ * @see #getRequestedOverrideBounds()
*/
public void getRequestedOverrideBounds(Rect outBounds) {
outBounds.set(getRequestedOverrideBounds());
@@ -295,19 +320,25 @@ public abstract class ConfigurationContainer {
* {@link #getRequestedOverrideBounds()}. If
* an empty {@link Rect} or null is specified, this container will be considered to match its
* parent bounds {@see #matchParentBounds} and will inherit bounds from its parent.
+ *
* @param bounds The bounds defining the container size.
+ *
* @return a bitmask representing the types of changes made to the bounds.
*/
public int setBounds(Rect bounds) {
int boundsChange = diffRequestedOverrideBounds(bounds);
+ final boolean overrideMaxBounds = providesMaxBounds()
+ && diffRequestedOverrideMaxBounds(bounds) != BOUNDS_CHANGE_NONE;
- if (boundsChange == BOUNDS_CHANGE_NONE) {
+ if (boundsChange == BOUNDS_CHANGE_NONE && !overrideMaxBounds) {
return boundsChange;
}
-
mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
mRequestsTmpConfig.windowConfiguration.setBounds(bounds);
+ if (overrideMaxBounds) {
+ mRequestsTmpConfig.windowConfiguration.setMaxBounds(bounds);
+ }
onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
return boundsChange;
@@ -318,6 +349,40 @@ public abstract class ConfigurationContainer {
return setBounds(mTmpRect);
}
+ /**
+ * Returns {@code true} if this {@link ConfigurationContainer} provides the maximum bounds to
+ * its child {@link ConfigurationContainer}s. Returns {@code false}, otherwise.
+ *
+ * The maximum bounds is how large a window can be expanded. Currently only
+ * {@link DisplayContent} and {@link DisplayArea} effect this property.
+ *
+ */
+ protected boolean providesMaxBounds() {
+ return false;
+ }
+
+ int diffRequestedOverrideMaxBounds(Rect bounds) {
+ if (equivalentRequestedOverrideMaxBounds(bounds)) {
+ return BOUNDS_CHANGE_NONE;
+ }
+
+ int boundsChange = BOUNDS_CHANGE_NONE;
+
+ final Rect existingBounds = getRequestedOverrideMaxBounds();
+
+ if (bounds == null || existingBounds.left != bounds.left
+ || existingBounds.top != bounds.top) {
+ boundsChange |= BOUNDS_CHANGE_POSITION;
+ }
+
+ if (bounds == null || existingBounds.width() != bounds.width()
+ || existingBounds.height() != bounds.height()) {
+ boundsChange |= BOUNDS_CHANGE_SIZE;
+ }
+
+ return boundsChange;
+ }
+
int diffRequestedOverrideBounds(Rect bounds) {
if (equivalentRequestedOverrideBounds(bounds)) {
return BOUNDS_CHANGE_NONE;
@@ -340,10 +405,6 @@ public abstract class ConfigurationContainer {
return boundsChange;
}
- boolean hasOverrideConfiguration() {
- return mHasOverrideConfiguration;
- }
-
public WindowConfiguration getWindowConfiguration() {
return mFullConfiguration.windowConfiguration;
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 546c5d4c29deb..6ffb482820171 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -25,6 +25,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static android.window.DisplayAreaOrganizer.FEATURE_WINDOW_TOKENS;
import static com.android.internal.util.Preconditions.checkState;
+import static com.android.server.wm.DisplayAreaProto.IS_TASK_DISPLAY_AREA;
import static com.android.server.wm.DisplayAreaProto.NAME;
import static com.android.server.wm.DisplayAreaProto.WINDOW_CONTAINER;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
@@ -194,6 +195,7 @@ public class DisplayArea extends WindowContainer {
final long token = proto.start(fieldId);
super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
proto.write(NAME, mName);
+ proto.write(IS_TASK_DISPLAY_AREA, isTaskDisplayArea());
proto.end(token);
}
@@ -349,6 +351,15 @@ public class DisplayArea extends WindowContainer {
return info;
}
+ @Override
+ public boolean providesMaxBounds() {
+ return true;
+ }
+
+ protected boolean isTaskDisplayArea() {
+ return false;
+ }
+
/**
* DisplayArea that contains WindowTokens, and orders them according to their type.
*/
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 66a9cf0bf5a45..77fc2283d443f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1930,6 +1930,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode, config);
calculateBounds(displayInfo, mTmpBounds);
config.windowConfiguration.setBounds(mTmpBounds);
+ config.windowConfiguration.setMaxBounds(mTmpBounds);
config.windowConfiguration.setWindowingMode(getWindowingMode());
config.windowConfiguration.setDisplayWindowingMode(getWindowingMode());
@@ -5389,6 +5390,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
});
}
+ @Override
+ public boolean providesMaxBounds() {
+ return true;
+ }
+
/** The entry for proceeding to handle {@link #mFixedRotationLaunchingApp}. */
class FixedRotationTransitionListener extends WindowManagerInternal.AppTransitionListener {
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index c43b780476398..a847744247c71 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1844,6 +1844,10 @@ final class TaskDisplayArea extends DisplayArea {
return lastReparentedStack;
}
+ @Override
+ protected boolean isTaskDisplayArea() {
+ return true;
+ }
@Override
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ConfigurationContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/ConfigurationContainerTests.java
index bcd9371510464..5828d02948a14 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ConfigurationContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ConfigurationContainerTests.java
@@ -33,6 +33,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -324,6 +325,47 @@ public class ConfigurationContainerTests {
assertEquals(100, listener.mOverrideConfiguration.smallestScreenWidthDp);
}
+ @Test
+ public void testSetMaxBoundsByHierarchy() {
+ final TestConfigurationContainer root =
+ new TestConfigurationContainer(true /* providesMaxBounds */);
+ final Rect bounds = new Rect(0, 0, 10, 10);
+ final TestConfigurationContainer child = new TestConfigurationContainer();
+ root.addChild(child);
+
+ root.setBounds(bounds);
+
+ assertEquals(bounds, root.getBounds());
+ assertEquals(bounds, root.getConfiguration().windowConfiguration.getBounds());
+ assertEquals(bounds, child.getBounds());
+ assertEquals(bounds, child.getConfiguration().windowConfiguration.getBounds());
+
+ assertEquals(bounds, root.getMaxBounds());
+ assertEquals(bounds, root.getConfiguration().windowConfiguration.getMaxBounds());
+ assertEquals(bounds, child.getMaxBounds());
+ assertEquals(bounds, child.getConfiguration().windowConfiguration.getMaxBounds());
+ }
+
+ @Test
+ public void testSetBoundsNotOverrideMaxBounds() {
+ final TestConfigurationContainer root = new TestConfigurationContainer();
+ final Rect bounds = new Rect(0, 0, 10, 10);
+ final TestConfigurationContainer child = new TestConfigurationContainer();
+ root.addChild(child);
+
+ root.setBounds(bounds);
+
+ assertEquals(bounds, root.getBounds());
+ assertEquals(bounds, root.getConfiguration().windowConfiguration.getBounds());
+ assertEquals(bounds, child.getBounds());
+ assertEquals(bounds, child.getConfiguration().windowConfiguration.getBounds());
+
+ assertTrue(root.getMaxBounds().isEmpty());
+ assertTrue(root.getConfiguration().windowConfiguration.getMaxBounds().isEmpty());
+ assertTrue(child.getMaxBounds().isEmpty());
+ assertTrue(child.getConfiguration().windowConfiguration.getMaxBounds().isEmpty());
+ }
+
/**
* Contains minimal implementation of {@link ConfigurationContainer}'s abstract behavior needed
* for testing.
@@ -333,6 +375,14 @@ public class ConfigurationContainerTests {
private List mChildren = new ArrayList<>();
private TestConfigurationContainer mParent;
+ private boolean mProvidesMaxBounds = false;
+
+ TestConfigurationContainer() {}
+
+ TestConfigurationContainer(boolean providesMaxBounds) {
+ mProvidesMaxBounds = providesMaxBounds;
+ }
+
TestConfigurationContainer addChild(TestConfigurationContainer childContainer) {
final ConfigurationContainer oldParent = childContainer.getParent();
childContainer.mParent = this;
@@ -369,6 +419,11 @@ public class ConfigurationContainerTests {
protected ConfigurationContainer getParent() {
return mParent;
}
+
+ @Override
+ public boolean providesMaxBounds() {
+ return mProvidesMaxBounds;
+ }
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index c8ed87db9d3eb..e17601e99010c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -40,8 +41,10 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import android.graphics.Rect;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
import com.google.android.collect.Lists;
@@ -63,7 +66,6 @@ import java.util.function.Function;
*/
@Presubmit
public class DisplayAreaTest {
-
@Rule
public SystemServicesTestRule mWmsRule = new SystemServicesTestRule();
@@ -379,6 +381,42 @@ public class DisplayAreaTest {
assertThat(result).isEqualTo(tda1);
}
+ @Test
+ public void testSetMaxBounds() {
+ final Rect parentBounds = new Rect(0, 0, 100, 100);
+ final Rect childBounds1 = new Rect(parentBounds.left, parentBounds.top,
+ parentBounds.right / 2, parentBounds.bottom);
+ final Rect childBounds2 = new Rect(parentBounds.right / 2, parentBounds.top,
+ parentBounds.right, parentBounds.bottom);
+ TestDisplayArea parentDa = new TestDisplayArea(mWms, parentBounds);
+ TestDisplayArea childDa1 = new TestDisplayArea(mWms, childBounds1);
+ TestDisplayArea childDa2 = new TestDisplayArea(mWms, childBounds2);
+ parentDa.addChild(childDa1, 0);
+ parentDa.addChild(childDa2, 1);
+
+ assertEquals(parentBounds, parentDa.getMaxBounds());
+ assertEquals(childBounds1, childDa1.getMaxBounds());
+ assertEquals(childBounds2, childDa2.getMaxBounds());
+
+ final WindowToken windowToken = createWindowToken(TYPE_APPLICATION);
+ childDa1.addChild(windowToken, 0);
+
+ assertEquals("DisplayArea's children must have the same max bounds as itself",
+ childBounds1, windowToken.getMaxBounds());
+ }
+
+ private static class TestDisplayArea extends DisplayArea {
+ private TestDisplayArea(WindowManagerService wms, Rect bounds) {
+ super(wms, ANY, "half display area");
+ setBounds(bounds);
+ }
+
+ @Override
+ SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+ return new MockSurfaceControlBuilder();
+ }
+ }
+
private WindowToken createWindowToken(int type) {
return new WindowToken(mWmsRule.getWindowManagerService(), new Binder(),
type, false /* persist */, null /* displayContent */,