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/api/current.txt b/api/current.txt
index b400b34..1b345d0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -879,6 +879,7 @@
     field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
     field public static final int matchOrder = 16843855; // 0x101044f
     field public static final int max = 16843062; // 0x1010136
+    field public static final int maxAspectRatio = 16844131; // 0x1010563
     field public static final int maxButtonHeight = 16844029; // 0x10104fd
     field public static final int maxDate = 16843584; // 0x1010340
     field public static final int maxEms = 16843095; // 0x1010157
diff --git a/api/system-current.txt b/api/system-current.txt
index 844b897..add55f5 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -992,6 +992,7 @@
     field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
     field public static final int matchOrder = 16843855; // 0x101044f
     field public static final int max = 16843062; // 0x1010136
+    field public static final int maxAspectRatio = 16844131; // 0x1010563
     field public static final int maxButtonHeight = 16844029; // 0x10104fd
     field public static final int maxDate = 16843584; // 0x1010340
     field public static final int maxEms = 16843095; // 0x1010157
diff --git a/api/test-current.txt b/api/test-current.txt
index a73ac18..2e1fd3a0 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -879,6 +879,7 @@
     field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
     field public static final int matchOrder = 16843855; // 0x101044f
     field public static final int max = 16843062; // 0x1010136
+    field public static final int maxAspectRatio = 16844131; // 0x1010563
     field public static final int maxButtonHeight = 16844029; // 0x10104fd
     field public static final int maxDate = 16843584; // 0x1010340
     field public static final int maxEms = 16843095; // 0x1010157
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 6dbe5fb..b01e6a1 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -223,6 +223,15 @@
     public int resizeMode = RESIZE_MODE_RESIZEABLE;
 
     /**
+     * Value indicating the maximum aspect ratio the activity supports.
+     * <p>
+     * 0 means unset.
+     * @See {@link android.R.attr#maxAspectRatio}.
+     * @hide
+     */
+    public float maxAspectRatio;
+
+    /**
      * Name of the VrListenerService component to run for this activity.
      * @see android.R.attr#enableVrMode
      * @hide
@@ -922,6 +931,7 @@
         requestedVrComponent = orig.requestedVrComponent;
         rotationAnimation = orig.rotationAnimation;
         colorMode = orig.colorMode;
+        maxAspectRatio = orig.maxAspectRatio;
     }
 
     /**
@@ -1064,6 +1074,9 @@
         if (requestedVrComponent != null) {
             pw.println(prefix + "requestedVrComponent=" + requestedVrComponent);
         }
+        if (maxAspectRatio != 0) {
+            pw.println(prefix + "maxAspectRatio=" + maxAspectRatio);
+        }
         super.dumpBack(pw, prefix, flags);
     }
 
@@ -1110,6 +1123,7 @@
         dest.writeString(requestedVrComponent);
         dest.writeInt(rotationAnimation);
         dest.writeInt(colorMode);
+        dest.writeFloat(maxAspectRatio);
     }
 
     public static final Parcelable.Creator<ActivityInfo> CREATOR
@@ -1146,6 +1160,7 @@
         requestedVrComponent = source.readString();
         rotationAnimation = source.readInt();
         colorMode = source.readInt();
+        maxAspectRatio = source.readFloat();
     }
 
     /**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index ffc7719..939e20f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -603,6 +603,15 @@
     public int largestWidthLimitDp = 0;
 
     /**
+     * Value indicating the maximum aspect ratio the application supports.
+     * <p>
+     * 0 means unset.
+     * @See {@link android.R.attr#maxAspectRatio}.
+     * @hide
+     */
+    public float maxAspectRatio;
+
+    /**
      * UUID of the storage volume on which this application is being hosted. For
      * apps hosted on the default internal storage at
      * {@link Environment#getDataDirectory()}, the UUID value is {@code null}.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index fb69986..1fafe65 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -37,6 +37,7 @@
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
+import static android.os.Build.VERSION_CODES.O;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
 
@@ -147,6 +148,8 @@
     public static final int APK_SIGNING_V1 = 1;
     public static final int APK_SIGNING_V2 = 2;
 
+    private static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f;
+
     // TODO: switch outError users to PackageParserException
     // TODO: refactor "codePath" to "apkPath"
 
@@ -3465,6 +3468,8 @@
             ai.privateFlags |= PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION;
         }
 
+        ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0);
+
         ai.networkSecurityConfigRes = sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestApplication_networkSecurityConfig,
                 0);
@@ -4161,6 +4166,8 @@
                 a.info.flags |= FLAG_ALWAYS_FOCUSABLE;
             }
 
+            setActivityMaxAspectRatio(a.info, sa, owner);
+
             a.info.lockTaskLaunchMode =
                     sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0);
 
@@ -4376,6 +4383,27 @@
         }
     }
 
+    private void setActivityMaxAspectRatio(ActivityInfo aInfo, TypedArray sa, Package owner) {
+        if (aInfo.resizeMode == RESIZE_MODE_RESIZEABLE
+                || aInfo.resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) {
+            // Resizeable activities can be put in any aspect ratio.
+            aInfo.maxAspectRatio = 0;
+            return;
+        }
+
+        // Default to (1.86) 16.7:9 aspect ratio for pre-O apps and unset for O and greater.
+        // NOTE: 16.7:9 was the max aspect ratio Android devices can support pre-O per the CDD.
+        float defaultMaxAspectRatio = owner.applicationInfo.targetSdkVersion < O
+                ? DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0;
+        if (owner.applicationInfo.maxAspectRatio != 0 ) {
+            // Use the application max aspect ration as default if set.
+            defaultMaxAspectRatio = owner.applicationInfo.maxAspectRatio;
+        }
+
+        aInfo.maxAspectRatio = Math.max(1.0f, sa.getFloat(
+                R.styleable.AndroidManifestActivity_maxAspectRatio, defaultMaxAspectRatio));
+    }
+
     /**
      * @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml.
      * @param restartOnConfigChanges The bit mask restartOnConfigChanges fetched from
@@ -4512,6 +4540,7 @@
         info.maxRecents = target.info.maxRecents;
         info.windowLayout = target.info.windowLayout;
         info.resizeMode = target.info.resizeMode;
+        info.maxAspectRatio = target.info.maxAspectRatio;
         info.encryptionAware = info.directBootAware = target.info.directBootAware;
 
         Activity a = new Activity(mParseActivityAliasArgs, info);
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b9409f2..ed5a42b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1191,6 +1191,20 @@
          <p>The default value is <code>false</code>.  -->
     <attr name="supportsPictureInPicture" format="boolean" />
 
+    <!-- This value indicates the maximum aspect ratio the activity supports. If the app runs on a
+         device with a wider aspect ratio, the system automatically letterboxes the app, leaving
+         portions of the screen unused so the app can run at its specified maximum aspect ratio.
+         <p>
+         Maximum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal
+         form. For example, if the maximum aspect ratio is 7:3, set value to 2.33.
+         <p>
+         Value needs to be greater or equal to 1.0, otherwise it is ignored.
+         <p>
+         NOTE: This attribute is ignored if the activity has
+         {@link android.R.attr#resizeableActivity} set to true, since that means your activity
+         supports any size. -->
+    <attr name="maxAspectRatio" format="float" />
+
     <!-- This value indicates how tasks rooted at this activity will behave in lockTask mode.
          While in lockTask mode the system will not launch non-permitted tasks until
          lockTask mode is disabled.
@@ -1408,6 +1422,7 @@
         <attr name="defaultToDeviceProtectedStorage" format="boolean" />
         <attr name="directBootAware" />
         <attr name="resizeableActivity" />
+        <attr name="maxAspectRatio" />
         <attr name="networkSecurityConfig" />
         <!-- Declare the category of this app. Categories are used to cluster multiple apps
              together into meaningful groups, such as when summarizing battery, network, or
@@ -2066,6 +2081,7 @@
         <attr name="resumeWhilePausing" />
         <attr name="resizeableActivity" />
         <attr name="supportsPictureInPicture" />
+        <attr name="maxAspectRatio" />
         <attr name="lockTaskMode" />
         <attr name="showForAllUsers" />
         <attr name="directBootAware" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index d6b5527..876d44d 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2812,6 +2812,7 @@
         <public name="fontProviderCerts" />
         <public name="iconTint" />
         <public name="iconTintMode" />
+        <public name="maxAspectRatio"/>
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
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);
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 74557e2..80b2e7d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -361,6 +361,18 @@
     }
 
     @Test
+    public void testOverrideConfigurationAncestorNotification() {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer grandparent = builder.setLayer(0).build();
+
+        final TestWindowContainer parent = grandparent.addChildWindow();
+        final TestWindowContainer child = parent.addChildWindow();
+        child.onOverrideConfigurationChanged(new Configuration());
+
+        assertTrue(grandparent.mOnDescendantOverrideCalled);
+    }
+
+    @Test
     public void testRemoveChild() throws Exception {
         final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
         final TestWindowContainer root = builder.setLayer(0).build();
@@ -718,6 +730,7 @@
         private Integer mOrientation;
 
         private boolean mOnParentSetCalled;
+        private boolean mOnDescendantOverrideCalled;
 
         /**
          * Compares 2 window layers and returns -1 if the first is lesser than the second in terms
@@ -776,6 +789,12 @@
         }
 
         @Override
+        void onDescendantOverrideConfigurationChanged() {
+            mOnDescendantOverrideCalled = true;
+            super.onDescendantOverrideConfigurationChanged();
+        }
+
+        @Override
         boolean isAnimating() {
             return mIsAnimating || super.isAnimating();
         }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index 28b6e45..1b1984d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -23,7 +23,6 @@
 import android.app.ActivityManager.TaskDescription;
 import android.content.Context;
 import android.graphics.Rect;
-import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
@@ -34,7 +33,6 @@
 import android.view.WindowManager;
 
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
 import static android.view.WindowManager.LayoutParams.FILL_PARENT;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -47,9 +45,8 @@
 @SmallTest
 @Presubmit
 @RunWith(AndroidJUnit4.class)
-public class WindowFrameTests {
+public class WindowFrameTests extends WindowTestsBase {
 
-    private static WindowManagerService sWm = null;
     private WindowToken mWindowToken;
     private final IWindow mIWindow = new TestIWindow();
 
@@ -105,8 +102,7 @@
         // Just any non zero value.
         sWm.mSystemDecorLayer = 10000;
 
-        mWindowToken = new WindowToken(sWm, new Binder(), 0, false,
-                sWm.getDefaultDisplayContentLocked(), false /* ownerCanManageAppTokens */);
+        mWindowToken = new TestAppWindowToken(sWm.getDefaultDisplayContentLocked());
         mStubStack = new TaskStack(sWm, 0);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 6f78245..48799d2 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -303,17 +303,19 @@
     static class TestAppWindowToken extends AppWindowToken {
 
         TestAppWindowToken(DisplayContent dc) {
-            super(sWm, null, false, dc, true /* fillsParent */);
+            super(sWm, null, false, dc, true /* fillsParent */, null /* overrideConfig */,
+                    null /* bounds */);
         }
 
         TestAppWindowToken(WindowManagerService service, IApplicationToken token,
                 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) {
             super(service, token, voiceInteraction, dc, inputDispatchingTimeoutNanos, fullscreen,
                     showForAllUsers, targetSdk, orientation, rotationAnimationHint, configChanges,
-                    launchTaskBehind, alwaysFocusable, controller);
+                    launchTaskBehind, alwaysFocusable, controller, overrideConfig, bounds);
         }
 
         int getWindowsCount() {
@@ -428,7 +430,8 @@
                     true /* showForAllUsers */, 0 /* configChanges */, false /* voiceInteraction */,
                     false /* launchTaskBehind */, false /* alwaysFocusable */,
                     0 /* targetSdkVersion */, 0 /* rotationAnimationHint */,
-                    0 /* inputDispatchingTimeoutNanos */, sWm);
+                    0 /* inputDispatchingTimeoutNanos */, sWm, null /* overrideConfig */,
+                    null /* bounds */);
             mToken = token;
         }
 
@@ -437,12 +440,13 @@
                 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 TestAppWindowToken(service, token, voiceInteraction, dc,
                     inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk,
                     orientation,
                     rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
-                    controller);
+                    controller, overrideConfig, bounds);
         }
 
         AppWindowToken getAppWindowToken() {