Introducing WindowConfiguration

Used to house configurations that don't affect resources and that
we don't want to communicate to apps, but need to be propagated
along side Configuration.

Test: bit FrameworksServicesTests:com.android.server.wm.WindowConfigurationTests
Test: adb shell am instrument -w -e package com.android.server.wm com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
Change-Id: I97de3efbe6d5e1d3b07156a8cfbce9be5eae5cb5
diff --git a/core/java/android/app/WindowConfiguration.aidl b/core/java/android/app/WindowConfiguration.aidl
new file mode 100644
index 0000000..1a70f52
--- /dev/null
+++ b/core/java/android/app/WindowConfiguration.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.app;
+
+parcelable WindowConfiguration;
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
new file mode 100644
index 0000000..86abaa3
--- /dev/null
+++ b/core/java/android/app/WindowConfiguration.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.DisplayInfo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class that contains windowing configuration/state for other objects that contain windows directly
+ * or indirectly. E.g. Activities, Task, Displays, ...
+ * The test class is {@link com.android.server.wm.WindowConfigurationTests} which must be kept
+ * up-to-date and ran anytime changes are made to this class.
+ * @hide
+ */
+public class WindowConfiguration implements Parcelable, Comparable<WindowConfiguration> {
+
+    /**
+     * {@link android.graphics.Rect} defining app bounds. The dimensions override usages of
+     * {@link DisplayInfo#appHeight} and {@link DisplayInfo#appWidth} and mirrors these values at
+     * the display level. Lower levels can override these values to provide custom bounds to enforce
+     * features such as a max aspect ratio.
+     */
+    private Rect mAppBounds;
+
+    @IntDef(flag = true,
+            value = {
+                    WINDOW_CONFIG_APP_BOUNDS,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface WindowConfig {}
+
+    /** Bit that indicates that the {@link #mAppBounds} changed. */
+    public static final int WINDOW_CONFIG_APP_BOUNDS = 1 << 0;
+
+    public WindowConfiguration() {
+        unset();
+    }
+
+    public WindowConfiguration(WindowConfiguration configuration) {
+        setTo(configuration);
+    }
+
+    private WindowConfiguration(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mAppBounds, flags);
+    }
+
+    private void readFromParcel(Parcel source) {
+        mAppBounds = source.readParcelable(Rect.class.getClassLoader());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<WindowConfiguration> CREATOR = new Creator<WindowConfiguration>() {
+        @Override
+        public WindowConfiguration createFromParcel(Parcel in) {
+            return new WindowConfiguration(in);
+        }
+
+        @Override
+        public WindowConfiguration[] newArray(int size) {
+            return new WindowConfiguration[size];
+        }
+    };
+
+    /**
+     * Set {@link #mAppBounds} to the input Rect.
+     * @param rect The rect value to set {@link #mAppBounds} to.
+     * @see #getAppBounds()
+     */
+    public void setAppBounds(Rect rect) {
+        if (rect == null) {
+            mAppBounds = null;
+            return;
+        }
+
+        setAppBounds(rect.left, rect.top, rect.right, rect.bottom);
+    }
+
+    /**
+     * @see #setAppBounds(Rect)
+     * @see #getAppBounds()
+     */
+    public void setAppBounds(int left, int top, int right, int bottom) {
+        if (mAppBounds == null) {
+            mAppBounds = new Rect();
+        }
+
+        mAppBounds.set(left, top, right, bottom);
+    }
+
+    /**
+     * @see #setAppBounds(Rect)
+     */
+    public Rect getAppBounds() {
+        return mAppBounds;
+    }
+
+    public void setTo(WindowConfiguration other) {
+        setAppBounds(other.mAppBounds);
+    }
+
+    /** Set this object to completely undefined. */
+    public void unset() {
+        setToDefaults();
+    }
+
+    public void setToDefaults() {
+        setAppBounds(null);
+    }
+
+    /**
+     * Copies the fields from delta into this Configuration object, keeping
+     * track of which ones have changed. Any undefined fields in {@code delta}
+     * are ignored and not copied in to the current Configuration.
+     *
+     * @return a bit mask of the changed fields, as per {@link #diff}
+     */
+    public @WindowConfig int updateFrom(@NonNull WindowConfiguration delta) {
+        int changed = 0;
+        if (delta.mAppBounds != null && !delta.mAppBounds.equals(mAppBounds)) {
+            changed |= WINDOW_CONFIG_APP_BOUNDS;
+            setAppBounds(delta.mAppBounds);
+        }
+        return changed;
+    }
+
+    /**
+     * Return a bit mask of the differences between this Configuration object and the given one.
+     * Does not change the values of either. Any undefined fields in <var>other</var> are ignored.
+     * @param other The configuration to diff against.
+     * @param compareUndefined If undefined values should be compared.
+     * @return Returns a bit mask indicating which configuration
+     * values has changed, containing any combination of {@link WindowConfig} flags.
+     *
+     * @see Configuration#diff(Configuration)
+     */
+    public @WindowConfig long diff(WindowConfiguration other, boolean compareUndefined) {
+        long changes = 0;
+
+        // Make sure that one of the values is not null and that they are not equal.
+        if ((compareUndefined || other.mAppBounds != null)
+                && mAppBounds != other.mAppBounds
+                && (mAppBounds == null || !mAppBounds.equals(other.mAppBounds))) {
+            changes |= WINDOW_CONFIG_APP_BOUNDS;
+        }
+
+        return changes;
+    }
+
+    @Override
+    public int compareTo(WindowConfiguration that) {
+        int n = 0;
+        if (mAppBounds == null && that.mAppBounds != null) {
+            return 1;
+        } else if (mAppBounds != null && that.mAppBounds == null) {
+            return -1;
+        } else if (mAppBounds != null && that.mAppBounds != null) {
+            n = mAppBounds.left - that.mAppBounds.left;
+            if (n != 0) return n;
+            n = mAppBounds.top - that.mAppBounds.top;
+            if (n != 0) return n;
+            n = mAppBounds.right - that.mAppBounds.right;
+            if (n != 0) return n;
+            n = mAppBounds.bottom - that.mAppBounds.bottom;
+            if (n != 0) return n;
+        }
+
+        // if (n != 0) return n;
+        return n;
+    }
+
+    @Override
+    public boolean equals(Object that) {
+        if (that == null) return false;
+        if (that == this) return true;
+        if (!(that instanceof WindowConfiguration)) {
+            return false;
+        }
+        return this.compareTo((WindowConfiguration) that) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 0;
+        if (mAppBounds != null) {
+            result = 31 * result + mAppBounds.hashCode();
+        }
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "{mAppBounds=" + mAppBounds + "}";
+    }
+}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index ca5fa6b..92f4849 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -780,6 +780,13 @@
      * constant starts at the high bits.
      */
     public static final int CONFIG_FONT_SCALE = 0x40000000;
+    /**
+     * Bit indicating changes to window configuration that isn't exposed to apps.
+     * This is for internal use only and apps don't handle it.
+     * @hide
+     * {@link Configuration}.
+     */
+    public static final int CONFIG_WINDOW_CONFIGURATION = 0x20000000;
 
     /** @hide
      * Unfortunately the constants for config changes in native code are
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index f7cccd5..780e6f7 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -19,15 +19,14 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.WindowConfiguration;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
-import android.graphics.Rect;
 import android.os.Build;
 import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
-import android.view.DisplayInfo;
 import android.view.View;
 
 import com.android.internal.util.XmlUtils;
@@ -42,7 +41,6 @@
 import java.util.ArrayList;
 import java.util.Locale;
 
-
 /**
  * This class describes all device configuration information that can
  * impact the resources the application retrieves.  This includes both
@@ -298,13 +296,11 @@
 
     /**
      * @hide
-     * {@link android.graphics.Rect} defining app bounds. The dimensions override usages of
-     * {@link DisplayInfo#appHeight} and {@link DisplayInfo#appWidth} and mirrors these values at
-     * the display level. Lower levels can override these values to provide custom bounds to enforce
-     * features such as a max aspect ratio.
-     * TODO(b/36812336): Move appBounds out of {@link Configuration}.
+     * Configuration relating to the windowing state of the object associated with this
+     * Configuration. Contents of this field are not intended to affect resources, but need to be
+     * communicated and propagated at the same time as the rest of Configuration.
      */
-    public Rect appBounds;
+    public final WindowConfiguration windowConfiguration = new WindowConfiguration();
 
     /** @hide */
     static public int resetScreenLayout(int curLayout) {
@@ -895,9 +891,9 @@
         compatScreenWidthDp = o.compatScreenWidthDp;
         compatScreenHeightDp = o.compatScreenHeightDp;
         compatSmallestScreenWidthDp = o.compatSmallestScreenWidthDp;
-        setAppBounds(o.appBounds);
         assetsSeq = o.assetsSeq;
         seq = o.seq;
+        windowConfiguration.setTo(o.windowConfiguration);
     }
 
     public String toString() {
@@ -1046,9 +1042,7 @@
             case NAVIGATIONHIDDEN_YES: sb.append("/h"); break;
             default: sb.append("/"); sb.append(navigationHidden); break;
         }
-        if (appBounds != null) {
-            sb.append(" appBounds="); sb.append(appBounds);
-        }
+        sb.append(" winConfig="); sb.append(windowConfiguration);
         if (assetsSeq != 0) {
             sb.append(" as.").append(assetsSeq);
         }
@@ -1083,8 +1077,8 @@
         smallestScreenWidthDp = compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
         densityDpi = DENSITY_DPI_UNDEFINED;
         assetsSeq = ASSETS_SEQ_UNDEFINED;
-        appBounds = null;
         seq = 0;
+        windowConfiguration.setToDefaults();
     }
 
     /**
@@ -1183,7 +1177,6 @@
             changed |= ActivityInfo.CONFIG_ORIENTATION;
             orientation = delta.orientation;
         }
-
         if (((delta.screenLayout & SCREENLAYOUT_SIZE_MASK) != SCREENLAYOUT_SIZE_UNDEFINED)
                 && (delta.screenLayout & SCREENLAYOUT_SIZE_MASK)
                 != (screenLayout & SCREENLAYOUT_SIZE_MASK)) {
@@ -1271,10 +1264,6 @@
         if (delta.compatSmallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
             compatSmallestScreenWidthDp = delta.compatSmallestScreenWidthDp;
         }
-        if (delta.appBounds != null && !delta.appBounds.equals(appBounds)) {
-            changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
-            setAppBounds(delta.appBounds);
-        }
         if (delta.assetsSeq != ASSETS_SEQ_UNDEFINED && delta.assetsSeq != assetsSeq) {
             changed |= ActivityInfo.CONFIG_ASSETS_PATHS;
             assetsSeq = delta.assetsSeq;
@@ -1282,6 +1271,9 @@
         if (delta.seq != 0) {
             seq = delta.seq;
         }
+        if (windowConfiguration.updateFrom(delta.windowConfiguration) != 0) {
+            changed |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
+        }
 
         return changed;
     }
@@ -1433,13 +1425,10 @@
             changed |= ActivityInfo.CONFIG_ASSETS_PATHS;
         }
 
-        // Make sure that one of the values is not null and that they are not equal.
-        if ((compareUndefined || delta.appBounds != null)
-                && appBounds != delta.appBounds
-                && (appBounds == null || (!publicOnly && !appBounds.equals(delta.appBounds))
-                        || (publicOnly && (appBounds.width() != delta.appBounds.width()
-                                || appBounds.height() != delta.appBounds.height())))) {
-            changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
+        // WindowConfiguration differences aren't considered public...
+        if (!publicOnly
+                && windowConfiguration.diff(delta.windowConfiguration, compareUndefined) != 0) {
+            changed |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
         }
 
         return changed;
@@ -1537,7 +1526,7 @@
         dest.writeInt(compatScreenWidthDp);
         dest.writeInt(compatScreenHeightDp);
         dest.writeInt(compatSmallestScreenWidthDp);
-        dest.writeValue(appBounds);
+        dest.writeValue(windowConfiguration);
         dest.writeInt(assetsSeq);
         dest.writeInt(seq);
     }
@@ -1573,7 +1562,7 @@
         compatScreenWidthDp = source.readInt();
         compatScreenHeightDp = source.readInt();
         compatSmallestScreenWidthDp = source.readInt();
-        appBounds = (Rect) source.readValue(null);
+        windowConfiguration.setTo((WindowConfiguration) source.readValue(null));
         assetsSeq = source.readInt();
         seq = source.readInt();
     }
@@ -1663,21 +1652,8 @@
         if (n != 0) return n;
         n = this.assetsSeq - that.assetsSeq;
         if (n != 0) return n;
-
-        if (this.appBounds == null && that.appBounds != null) {
-            return 1;
-        } else if (this.appBounds != null && that.appBounds == null) {
-            return -1;
-        } else if (this.appBounds != null && that.appBounds != null) {
-            n = this.appBounds.left - that.appBounds.left;
-            if (n != 0) return n;
-            n = this.appBounds.top - that.appBounds.top;
-            if (n != 0) return n;
-            n = this.appBounds.right - that.appBounds.right;
-            if (n != 0) return n;
-            n = this.appBounds.bottom - that.appBounds.bottom;
-            if (n != 0) return n;
-        }
+        n = windowConfiguration.compareTo(that.windowConfiguration);
+        if (n != 0) return n;
 
         // if (n != 0) return n;
         return n;
@@ -1768,33 +1744,6 @@
     /**
      * @hide
      *
-     * Helper method for setting the app bounds.
-     */
-    public void setAppBounds(Rect rect) {
-        if (rect == null) {
-            appBounds = null;
-            return;
-        }
-
-        setAppBounds(rect.left, rect.top, rect.right, rect.bottom);
-    }
-
-    /**
-     * @hide
-     *
-     * Helper method for setting the app bounds.
-     */
-    public void setAppBounds(int left, int top, int right, int bottom) {
-        if (appBounds == null) {
-            appBounds = new Rect();
-        }
-
-        appBounds.set(left, top, right, bottom);
-    }
-
-    /**
-     * @hide
-     *
      * Clears the locale without changing layout direction.
      */
     public void clearLocales() {
@@ -2282,6 +2231,10 @@
         if (base.assetsSeq != change.assetsSeq) {
             delta.assetsSeq = change.assetsSeq;
         }
+
+        if (!base.windowConfiguration.equals(change.windowConfiguration)) {
+            delta.windowConfiguration.setTo(change.windowConfiguration);
+        }
         return delta;
     }
 
@@ -2354,10 +2307,9 @@
                         SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
         configOut.densityDpi = XmlUtils.readIntAttribute(parser, XML_ATTR_DENSITY,
                 DENSITY_DPI_UNDEFINED);
-        configOut.appBounds =
-            Rect.unflattenFromString(XmlUtils.readStringAttribute(parser, XML_ATTR_APP_BOUNDS));
 
-        // For persistence, we don't care about assetsSeq, so do not read it out.
+        // For persistence, we don't care about assetsSeq and WindowConfiguration, so do not read it
+        // out.
     }
 
 
@@ -2427,11 +2379,7 @@
             XmlUtils.writeIntAttribute(xml, XML_ATTR_DENSITY, config.densityDpi);
         }
 
-        if (config.appBounds != null) {
-            XmlUtils.writeStringAttribute(xml, XML_ATTR_APP_BOUNDS,
-                config.appBounds.flattenToString());
-        }
-
-        // For persistence, we do not care about assetsSeq, so do not write it out.
+        // For persistence, we do not care about assetsSeq and window configuration, so do not write
+        // it out.
     }
 }
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index c5c6b37..b813ddb 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -23,6 +23,7 @@
 
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArraySet;
@@ -568,10 +569,10 @@
         outMetrics.xdpi = outMetrics.noncompatXdpi = physicalXDpi;
         outMetrics.ydpi = outMetrics.noncompatYdpi = physicalYDpi;
 
-        width = configuration != null && configuration.appBounds != null
-                ? configuration.appBounds.width() : width;
-        height = configuration != null && configuration.appBounds != null
-                ? configuration.appBounds.height() : height;
+        final Rect appBounds = configuration != null
+                ? configuration.windowConfiguration.getAppBounds() : null;
+        width = appBounds != null ? appBounds.width() : width;
+        height = appBounds != null ? appBounds.height() : height;
 
         outMetrics.noncompatWidthPixels  = outMetrics.widthPixels = width;
         outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height;
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index e9e073c..a2e3067 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -46,6 +46,7 @@
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
+import static android.content.pm.ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
 import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
 import static android.content.pm.ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
@@ -429,11 +430,11 @@
                                 pw.print("\"");
                         pw.print(" primaryColor=");
                         pw.println(Integer.toHexString(taskDescription.getPrimaryColor()));
-                        pw.print(prefix); pw.print("  backgroundColor=");
+                        pw.print(prefix + " backgroundColor=");
                         pw.println(Integer.toHexString(taskDescription.getBackgroundColor()));
-                        pw.print(prefix); pw.print("  statusBarColor=");
+                        pw.print(prefix + " statusBarColor=");
                         pw.println(Integer.toHexString(taskDescription.getStatusBarColor()));
-                        pw.print(prefix); pw.print("  navigationBarColor=");
+                        pw.print(prefix + " navigationBarColor=");
                         pw.println(Integer.toHexString(taskDescription.getNavigationBarColor()));
             }
             if (iconFilename == null && taskDescription.getIcon() != null) {
@@ -2311,9 +2312,9 @@
         // We must base this on the parent configuration, because we set our override
         // configuration's appBounds based on the result of this method. If we used our own
         // configuration, it would be influenced by past invocations.
-        final Configuration configuration = getParent().getConfiguration();
-        final int containingAppWidth = configuration.appBounds.width();
-        final int containingAppHeight = configuration.appBounds.height();
+        final Rect appBounds = getParent().getConfiguration().windowConfiguration.getAppBounds();
+        final int containingAppWidth = appBounds.width();
+        final int containingAppHeight = appBounds.height();
         int maxActivityWidth = containingAppWidth;
         int maxActivityHeight = containingAppHeight;
 
@@ -2342,8 +2343,7 @@
         outBounds.set(0, 0, maxActivityWidth, maxActivityHeight);
         // Position the activity frame on the opposite side of the nav bar.
         final int navBarPosition = service.mWindowManager.getNavBarPosition();
-        final int left = navBarPosition == NAV_BAR_LEFT
-                ? configuration.appBounds.right - outBounds.width() : 0;
+        final int left = navBarPosition == NAV_BAR_LEFT ? appBounds.right - outBounds.width() : 0;
         outBounds.offsetTo(left, 0 /* top */);
     }
 
@@ -2574,6 +2574,10 @@
                 changes &= ~CONFIG_SMALLEST_SCREEN_SIZE;
             }
         }
+        // We don't want window configuration to cause relaunches.
+        if ((changes & CONFIG_WINDOW_CONFIGURATION) != 0) {
+            changes &= ~CONFIG_WINDOW_CONFIGURATION;
+        }
 
         return changes;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2df773e..2e7f822 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1205,8 +1205,9 @@
         final int leftInset = mTmpRect.left;
         final int topInset = mTmpRect.top;
         // appBounds at the root level should mirror the app screen size.
-        config.setAppBounds(leftInset /*left*/, topInset /*top*/, leftInset + displayInfo.appWidth /*right*/,
-                topInset + displayInfo.appHeight /*bottom*/);
+        config.windowConfiguration.setAppBounds(leftInset /* left */, topInset /* top */,
+                leftInset + displayInfo.appWidth /* right */,
+                topInset + displayInfo.appHeight /* bottom */);
         final boolean rotated = (displayInfo.rotation == Surface.ROTATION_90
                 || displayInfo.rotation == Surface.ROTATION_270);
 
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 2b45d67..ce21991 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -241,8 +241,8 @@
             final int leftInset = mTmpRect.left;
             final int topInset = mTmpRect.top;
 
-            config.setAppBounds(leftInset /*left*/, topInset /*top*/, leftInset + appWidth /*right*/,
-                    topInset + appHeight /*bottom*/);
+            config.windowConfiguration.setAppBounds(leftInset /*left*/, topInset /*top*/,
+                    leftInset + appWidth /*right*/, topInset + appHeight /*bottom*/);
 
             config.screenWidthDp = (int)
                     (mService.mPolicy.getConfigDisplayWidth(dw, dh, rotation, baseConfig.uiMode,
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index 9c44c14..358b8be 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -16,11 +16,9 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 
 import android.app.ActivityManager.StackId;
-import android.app.RemoteAction;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -30,11 +28,9 @@
 import android.util.SparseArray;
 import android.view.DisplayInfo;
 
-import com.android.server.UiThread;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.ref.WeakReference;
-import java.util.List;
 
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -278,9 +274,9 @@
             int width;
             int height;
 
-            final Rect parentAppBounds = parentConfig.appBounds;
+            final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
 
-            config.setAppBounds(!bounds.isEmpty() ? bounds : null);
+            config.windowConfiguration.setAppBounds(!bounds.isEmpty() ? bounds : null);
             boolean intersectParentBounds = false;
 
             if (StackId.tasksAreFloating(mStackId)) {
@@ -295,7 +291,7 @@
                     nonDecorBounds.inset(mTmpNonDecorInsets);
                     // Move app bounds to zero to apply intersection with parent correctly. They are
                     // used only for evaluating width and height, so it's OK to move them around.
-                    config.appBounds.offsetTo(0, 0);
+                    config.windowConfiguration.getAppBounds().offsetTo(0, 0);
                     intersectParentBounds = true;
                 }
                 width = (int) (stableBounds.width() / density);
@@ -319,8 +315,8 @@
                 intersectParentBounds = true;
             }
 
-            if (intersectParentBounds && config.appBounds != null) {
-                config.appBounds.intersect(parentAppBounds);
+            if (intersectParentBounds && config.windowConfiguration.getAppBounds() != null) {
+                config.windowConfiguration.getAppBounds().intersect(parentAppBounds);
             }
 
             config.screenWidthDp = width;
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
index 2252c85..47a3a72 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
@@ -126,7 +126,7 @@
 
         // Verify with nav bar on the right.
         when(service.mWindowManager.getNavBarPosition()).thenReturn(navBarPosition);
-        task.getConfiguration().setAppBounds(taskBounds);
+        task.getConfiguration().windowConfiguration.setAppBounds(taskBounds);
         record.info.maxAspectRatio = aspectRatio;
         record.ensureActivityConfigurationLocked(0 /* globalChanges */, false /* preserveWindow */);
         assertEquals(expectedActivityBounds, record.getBounds());
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppBoundsTests.java b/services/tests/servicestests/src/com/android/server/wm/AppBoundsTests.java
deleted file mode 100644
index 432cfc7..0000000
--- a/services/tests/servicestests/src/com/android/server/wm/AppBoundsTests.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import android.app.ActivityManager;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.graphics.Rect;
-import android.view.DisplayInfo;
-import org.junit.Test;
-
-import android.platform.test.annotations.Presubmit;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Test class to exercise logic related to {@link android.content.res.Configuration#appBounds}.
- *
- * Build/Install/Run:
- *  bit FrameworksServicesTests:com.android.server.wm.AppBoundsTests
- */
-@SmallTest
-@Presubmit
-@org.junit.runner.RunWith(AndroidJUnit4.class)
-public class AppBoundsTests extends WindowTestsBase {
-    private Rect mParentBounds;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/);
-    }
-
-    /**
-     * Ensures that appBounds causes {@link android.content.pm.ActivityInfo.CONFIG_APP_BOUNDS} diff.
-     */
-    @Test
-    public void testAppBoundsConfigurationDiff() {
-        final Configuration config = new Configuration();
-        final Configuration config2 = new Configuration();
-        config.appBounds = new Rect(0, 1, 1, 0);
-        config2.appBounds = new Rect(1, 2, 2, 1);
-
-        assertEquals(ActivityInfo.CONFIG_SCREEN_SIZE, config.diff(config2));
-        assertEquals(0, config.diffPublicOnly(config2));
-    }
-
-    /**
-     * Ensures the configuration app bounds at the root level match the app dimensions.
-     */
-    @Test
-    public void testRootConfigurationBounds() throws Exception {
-        final DisplayInfo info = mDisplayContent.getDisplayInfo();
-        info.appWidth = 1024;
-        info.appHeight = 768;
-
-        final Configuration config = sWm.computeNewConfiguration(mDisplayContent.getDisplayId());
-        // The bounds should always be positioned in the top left.
-        assertEquals(config.appBounds.left, 0);
-        assertEquals(config.appBounds.top, 0);
-
-        // The bounds should equal the defined app width and height
-        assertEquals(config.appBounds.width(), info.appWidth);
-        assertEquals(config.appBounds.height(), info.appHeight);
-    }
-
-    /**
-     * Ensures that bounds are clipped to their parent.
-     */
-    @Test
-    public void testBoundsClipping() throws Exception {
-        final Rect shiftedBounds = new Rect(mParentBounds);
-        shiftedBounds.offset(10, 10);
-        final Rect expectedBounds = new Rect(mParentBounds);
-        expectedBounds.intersect(shiftedBounds);
-        testStackBoundsConfiguration(null /*stackId*/, mParentBounds, shiftedBounds,
-                expectedBounds);
-    }
-
-    /**
-     * Ensures that empty bounds are not propagated to the configuration.
-     */
-    @Test
-    public void testEmptyBounds() throws Exception {
-        final Rect emptyBounds = new Rect();
-        testStackBoundsConfiguration(null /*stackId*/, mParentBounds, emptyBounds,
-                null /*ExpectedBounds*/);
-    }
-
-    /**
-     * Ensures that bounds on freeform stacks are not clipped.
-     */
-    @Test
-    public void testFreeFormBounds() throws Exception {
-        final Rect freeFormBounds = new Rect(mParentBounds);
-        freeFormBounds.offset(10, 10);
-        testStackBoundsConfiguration(ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID,
-                mParentBounds, freeFormBounds, freeFormBounds);
-    }
-
-    /**
-     * Ensures that fully contained bounds are not clipped.
-     */
-    @Test
-    public void testContainedBounds() throws Exception {
-        final Rect insetBounds = new Rect(mParentBounds);
-        insetBounds.inset(5, 5, 5, 5);
-        testStackBoundsConfiguration(null /*stackId*/, mParentBounds, insetBounds, insetBounds);
-    }
-
-    /**
-     * Ensures that full screen free form bounds are clipped
-     */
-    @Test
-    public void testFullScreenFreeFormBounds() throws Exception {
-        final Rect fullScreenBounds = new Rect(0, 0, mDisplayInfo.logicalWidth,
-                mDisplayInfo.logicalHeight);
-        testStackBoundsConfiguration(null /*stackId*/, mParentBounds, fullScreenBounds,
-                mParentBounds);
-    }
-
-    private void testStackBoundsConfiguration(Integer stackId, Rect parentBounds, Rect bounds,
-            Rect expectedConfigBounds) {
-        final StackWindowController stackController = stackId != null ?
-                createStackControllerOnStackOnDisplay(stackId, mDisplayContent)
-                : createStackControllerOnDisplay(mDisplayContent);
-
-        final Configuration parentConfig = mDisplayContent.getConfiguration();
-        parentConfig.setAppBounds(parentBounds);
-
-        final Configuration config = new Configuration();
-        stackController.adjustConfigurationForBounds(bounds, null /*insetBounds*/,
-                new Rect() /*nonDecorBounds*/, new Rect() /*stableBounds*/, false /*overrideWidth*/,
-                false /*overrideHeight*/, mDisplayInfo.logicalDensityDpi, config, parentConfig);
-        // Assert that both expected and actual are null or are equal to each other
-
-        assertTrue((expectedConfigBounds == null && config.appBounds == null)
-                || expectedConfigBounds.equals(config.appBounds));
-    }
-
-    /**
-     * Ensures appBounds are considered in {@link Configuration#compareTo(Configuration)}.
-     */
-    @Test
-    public void testConfigurationCompareTo() throws Exception {
-        final Configuration blankConfig = new Configuration();
-
-        final Configuration config1 = new Configuration();
-        config1.appBounds = new Rect(1, 2, 3, 4);
-
-        final Configuration config2 = new Configuration(config1);
-
-        assertEquals(config1.compareTo(config2), 0);
-
-        config2.appBounds.left = 0;
-
-        // Different bounds
-        assertNotEquals(config1.compareTo(config2), 0);
-
-        // No bounds
-        assertEquals(config1.compareTo(blankConfig), -1);
-        assertEquals(blankConfig.compareTo(config1), 1);
-
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java
new file mode 100644
index 0000000..78643bd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import org.junit.Test;
+
+import android.app.WindowConfiguration;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.DisplayInfo;
+
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
+import static android.content.pm.ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test class to for {@link android.app.WindowConfiguration}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.WindowConfigurationTests
+ */
+@SmallTest
+@Presubmit
+@org.junit.runner.RunWith(AndroidJUnit4.class)
+public class WindowConfigurationTests extends WindowTestsBase {
+    private Rect mParentBounds;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/);
+    }
+
+    /** Tests {@link android.app.WindowConfiguration#diff(WindowConfiguration, boolean)}. */
+    @Test
+    public void testDiff() {
+        final Configuration config1 = new Configuration();
+        final WindowConfiguration winConfig1 = config1.windowConfiguration;
+        final Configuration config2 = new Configuration();
+        final WindowConfiguration winConfig2 = config2.windowConfiguration;
+        final Configuration config3 = new Configuration();
+        final WindowConfiguration winConfig3 = config3.windowConfiguration;
+
+        winConfig1.setAppBounds(0, 1, 1, 0);
+        winConfig2.setAppBounds(1, 2, 2, 1);
+        winConfig3.setAppBounds(winConfig1.getAppBounds());
+
+
+        assertEquals(CONFIG_WINDOW_CONFIGURATION, config1.diff(config2));
+        assertEquals(0, config1.diffPublicOnly(config2));
+        assertEquals(WINDOW_CONFIG_APP_BOUNDS,
+                winConfig1.diff(winConfig2, false /* compareUndefined */));
+
+        assertEquals(0, config1.diff(config3));
+        assertEquals(0, config1.diffPublicOnly(config3));
+        assertEquals(0, winConfig1.diff(winConfig3, false /* compareUndefined */));
+    }
+
+    /** Tests {@link android.app.WindowConfiguration#compareTo(WindowConfiguration)}. */
+    @Test
+    public void testConfigurationCompareTo() throws Exception {
+        final Configuration blankConfig = new Configuration();
+        final WindowConfiguration blankWinConfig = new WindowConfiguration();
+
+        final Configuration config1 = new Configuration();
+        final WindowConfiguration winConfig1 = config1.windowConfiguration;
+        winConfig1.setAppBounds(1, 2, 3, 4);
+
+        final Configuration config2 = new Configuration(config1);
+        final WindowConfiguration winConfig2 = config2.windowConfiguration;
+
+        assertEquals(config1.compareTo(config2), 0);
+        assertEquals(winConfig1.compareTo(winConfig2), 0);
+
+        // Different bounds
+        winConfig2.setAppBounds(0, 2, 3, 4);
+
+        assertNotEquals(config1.compareTo(config2), 0);
+        assertNotEquals(winConfig1.compareTo(winConfig2), 0);
+
+        // No bounds
+        assertEquals(config1.compareTo(blankConfig), -1);
+        assertEquals(winConfig1.compareTo(blankWinConfig), -1);
+
+        assertEquals(blankConfig.compareTo(config1), 1);
+        assertEquals(blankWinConfig.compareTo(winConfig1), 1);
+    }
+
+    /** Ensures the configuration app bounds at the root level match the app dimensions. */
+    @Test
+    public void testAppBounds_RootConfigurationBounds() throws Exception {
+        final DisplayInfo info = mDisplayContent.getDisplayInfo();
+        info.appWidth = 1024;
+        info.appHeight = 768;
+
+        final Rect appBounds = sWm.computeNewConfiguration(
+                mDisplayContent.getDisplayId()).windowConfiguration.getAppBounds();
+        // The bounds should always be positioned in the top left.
+        assertEquals(appBounds.left, 0);
+        assertEquals(appBounds.top, 0);
+
+        // The bounds should equal the defined app width and height
+        assertEquals(appBounds.width(), info.appWidth);
+        assertEquals(appBounds.height(), info.appHeight);
+    }
+
+    /** Ensures that bounds are clipped to their parent. */
+    @Test
+    public void testAppBounds_BoundsClipping() throws Exception {
+        final Rect shiftedBounds = new Rect(mParentBounds);
+        shiftedBounds.offset(10, 10);
+        final Rect expectedBounds = new Rect(mParentBounds);
+        expectedBounds.intersect(shiftedBounds);
+        testStackBoundsConfiguration(null /*stackId*/, mParentBounds, shiftedBounds,
+                expectedBounds);
+    }
+
+    /** Ensures that empty bounds are not propagated to the configuration. */
+    @Test
+    public void testAppBounds_EmptyBounds() throws Exception {
+        final Rect emptyBounds = new Rect();
+        testStackBoundsConfiguration(null /*stackId*/, mParentBounds, emptyBounds,
+                null /*ExpectedBounds*/);
+    }
+
+    /** Ensures that bounds on freeform stacks are not clipped. */
+    @Test
+    public void testAppBounds_FreeFormBounds() throws Exception {
+        final Rect freeFormBounds = new Rect(mParentBounds);
+        freeFormBounds.offset(10, 10);
+        testStackBoundsConfiguration(FREEFORM_WORKSPACE_STACK_ID, mParentBounds, freeFormBounds,
+                freeFormBounds);
+    }
+
+    /** Ensures that fully contained bounds are not clipped. */
+    @Test
+    public void testAppBounds_ContainedBounds() throws Exception {
+        final Rect insetBounds = new Rect(mParentBounds);
+        insetBounds.inset(5, 5, 5, 5);
+        testStackBoundsConfiguration(null /*stackId*/, mParentBounds, insetBounds, insetBounds);
+    }
+
+    /** Ensures that full screen free form bounds are clipped */
+    @Test
+    public void testAppBounds_FullScreenFreeFormBounds() throws Exception {
+        final Rect fullScreenBounds = new Rect(0, 0, mDisplayInfo.logicalWidth,
+                mDisplayInfo.logicalHeight);
+        testStackBoundsConfiguration(null /*stackId*/, mParentBounds, fullScreenBounds,
+                mParentBounds);
+    }
+
+    private void testStackBoundsConfiguration(Integer stackId, Rect parentBounds, Rect bounds,
+            Rect expectedConfigBounds) {
+        final StackWindowController stackController = stackId != null ?
+                createStackControllerOnStackOnDisplay(stackId, mDisplayContent)
+                : createStackControllerOnDisplay(mDisplayContent);
+
+        final Configuration parentConfig = mDisplayContent.getConfiguration();
+        parentConfig.windowConfiguration.setAppBounds(parentBounds);
+
+        final Configuration config = new Configuration();
+        final WindowConfiguration winConfig = config.windowConfiguration;
+        stackController.adjustConfigurationForBounds(bounds, null /*insetBounds*/,
+                new Rect() /*nonDecorBounds*/, new Rect() /*stableBounds*/, false /*overrideWidth*/,
+                false /*overrideHeight*/, mDisplayInfo.logicalDensityDpi, config, parentConfig);
+        // Assert that both expected and actual are null or are equal to each other
+
+        assertTrue((expectedConfigBounds == null && winConfig.getAppBounds() == null)
+                || expectedConfigBounds.equals(winConfig.getAppBounds()));
+    }
+
+}