Merge "Split PhoneWindowManager.beginLayoutLw."
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index f70423f..b44aab7 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -63,6 +63,7 @@
     final boolean mRetainInstance;
     final boolean mDetached;
     final Bundle mArguments;
+    final boolean mHidden;
 
     Bundle mSavedFragmentState;
 
@@ -78,6 +79,7 @@
         mRetainInstance = frag.mRetainInstance;
         mDetached = frag.mDetached;
         mArguments = frag.mArguments;
+        mHidden = frag.mHidden;
     }
 
     public FragmentState(Parcel in) {
@@ -90,6 +92,7 @@
         mRetainInstance = in.readInt() != 0;
         mDetached = in.readInt() != 0;
         mArguments = in.readBundle();
+        mHidden = in.readInt() != 0;
         mSavedFragmentState = in.readBundle();
     }
 
@@ -117,6 +120,7 @@
         mInstance.mTag = mTag;
         mInstance.mRetainInstance = mRetainInstance;
         mInstance.mDetached = mDetached;
+        mInstance.mHidden = mHidden;
         mInstance.mFragmentManager = host.mFragmentManager;
         if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
                 "Instantiated fragment " + mInstance);
@@ -138,6 +142,7 @@
         dest.writeInt(mRetainInstance ? 1 : 0);
         dest.writeInt(mDetached ? 1 : 0);
         dest.writeBundle(mArguments);
+        dest.writeInt(mHidden ? 1 : 0);
         dest.writeBundle(mSavedFragmentState);
     }
 
@@ -460,6 +465,9 @@
     // If set this fragment is being retained across the current config change.
     boolean mRetaining;
 
+    // If set this fragment's loaders are being retained across the current config change.
+    boolean mRetainLoader;
+
     // If set this fragment has menu items to contribute.
     boolean mHasMenu;
 
@@ -2407,7 +2415,7 @@
                 mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false);
             }
             if (mLoaderManager != null) {
-                if (mRetaining) {
+                if (mRetainLoader) {
                     mLoaderManager.doRetain();
                 } else {
                     mLoaderManager.doStop();
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index 28dadfa..1b45137 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -341,6 +341,7 @@
      */
     public void doLoaderStop(boolean retain) {
         mHost.doLoaderStop(retain);
+        mHost.mFragmentManager.setRetainLoader(retain);
     }
 
     /**
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 132ffef..51d6132 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -869,6 +869,17 @@
         }
     }
 
+    void setRetainLoader(boolean retain) {
+        if (mActive != null) {
+            for (int i=0; i<mActive.size(); i++) {
+                Fragment f = mActive.get(i);
+                if (f != null) {
+                    f.mRetainLoader = retain;
+                }
+            }
+        }
+    }
+
     void moveToState(Fragment f, int newState, int transit, int transitionStyle,
             boolean keepActive) {
         if (DEBUG && false) Log.v(TAG, "moveToState: " + f
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d7ecbfe..04b1a3b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -114,6 +114,12 @@
     /** File name in an APK for the Android manifest. */
     private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
 
+    /**
+     * File name in an APK for bytecode.  There may be additional bytecode files
+     * but this one is always required for an APK that has code.
+     */
+    private static final String BYTECODE_FILENAME = "classes.dex";
+
     /** Path prefix for apps on expanded storage */
     private static final String MNT_EXPAND = "/mnt/expand/";
 
@@ -615,6 +621,7 @@
     public final static int PARSE_IS_PRIVILEGED = 1<<7;
     public final static int PARSE_COLLECT_CERTIFICATES = 1<<8;
     public final static int PARSE_TRUSTED_OVERLAY = 1<<9;
+    public final static int PARSE_ENFORCE_CODE = 1<<10;
 
     private static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
 
@@ -1066,8 +1073,11 @@
 
     private static void collectCertificates(Package pkg, File apkFile, int flags)
             throws PackageParserException {
+        final boolean requireCode = ((flags & PARSE_ENFORCE_CODE) != 0)
+                && ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0);
         final String apkPath = apkFile.getAbsolutePath();
 
+        boolean codeFound = false;
         StrictJarFile jarFile = null;
         try {
             jarFile = new StrictJarFile(apkPath);
@@ -1089,13 +1099,23 @@
                     final ZipEntry entry = i.next();
 
                     if (entry.isDirectory()) continue;
-                    if (entry.getName().startsWith("META-INF/")) continue;
-                    if (entry.getName().equals(ANDROID_MANIFEST_FILENAME)) continue;
+
+                    final String entryName = entry.getName();
+                    if (entryName.startsWith("META-INF/")) continue;
+                    if (entryName.equals(ANDROID_MANIFEST_FILENAME)) continue;
+                    if (entryName.equals(BYTECODE_FILENAME)) {
+                        codeFound = true;
+                    }
 
                     toVerify.add(entry);
                 }
             }
 
+            if (!codeFound && requireCode) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                        "Package " + apkPath + " code is missing");
+            }
+
             // Verify that entries are signed consistently with the first entry
             // we encountered. Note that for splits, certificates may have
             // already been populated during an earlier parse of a base APK.
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 21ba7bd..121a187 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -359,6 +359,14 @@
         }
     }
 
+    public void requestColorTransform(int displayId, int colorTransformId) {
+        try {
+            mDm.requestColorTransform(displayId, colorTransformId);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to request color transform.", ex);
+        }
+    }
+
     public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection,
             String name, int width, int height, int densityDpi, Surface surface, int flags,
             VirtualDisplay.Callback callback, Handler handler) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 4486dd4..8a1abf1 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -59,6 +59,9 @@
     // No permissions required.
     WifiDisplayStatus getWifiDisplayStatus();
 
+    // Requires CONFIGURE_DISPLAY_COLOR_TRANSFORM
+    void requestColorTransform(int displayId, int colorTransformId);
+
     // Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate
     // MediaProjection token for certain combinations of flags.
     int createVirtualDisplay(in IVirtualDisplayCallback callback,
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index bad94fc..8e86a53 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -463,13 +463,15 @@
         public abstract long getCpuPowerMaUs(int which);
 
         /**
-         * Returns the approximate cpu time (in milliseconds) spent at a certain CPU speed.
+         * Returns the approximate cpu time (in milliseconds) spent at a certain CPU speed for a
+         * given CPU cluster.
+         * @param cluster the index of the CPU cluster.
          * @param step the index of the CPU speed. This is not the actual speed of the CPU.
          * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
-         * @see BatteryStats#getCpuSpeedSteps()
+         * @see PowerProfile.getNumCpuClusters()
+         * @see PowerProfile.getNumSpeedStepsInCpuCluster(int)
          */
-        @Deprecated
-        public abstract long getTimeAtCpuSpeed(int step, int which);
+        public abstract long getTimeAtCpuSpeed(int cluster, int step, int which);
 
         public static abstract class Sensor {
             /*
@@ -2276,9 +2278,6 @@
 
     public abstract Map<String, ? extends Timer> getKernelWakelockStats();
 
-    /** Returns the number of different speeds that the CPU can run at */
-    public abstract int getCpuSpeedSteps();
-
     public abstract void writeToParcelWithoutUids(Parcel out, int flags);
 
     private final static void formatTimeRaw(StringBuilder out, long seconds) {
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 35c4192..1269ad9 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -16,7 +16,10 @@
 
 package android.view;
 
+import android.annotation.RequiresPermission;
+import android.content.Context;
 import android.content.res.CompatibilityInfo;
+import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -30,6 +33,8 @@
 
 import java.util.Arrays;
 
+import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_TRANSFORM;
+
 /**
  * Provides information about the size and density of a logical display.
  * <p>
@@ -679,6 +684,49 @@
     }
 
     /**
+     * Request the display applies a color transform.
+     * @hide
+     */
+    @RequiresPermission(CONFIGURE_DISPLAY_COLOR_TRANSFORM)
+    public void requestColorTransform(ColorTransform colorTransform) {
+        mGlobal.requestColorTransform(mDisplayId, colorTransform.getId());
+    }
+
+    /**
+     * Returns the active color transform of this display
+     * @hide
+     */
+    public ColorTransform getColorTransform() {
+        synchronized (this) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.getColorTransform();
+        }
+    }
+
+    /**
+     * Returns the default color transform of this display
+     * @hide
+     */
+    public ColorTransform getDefaultColorTransform() {
+        synchronized (this) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.getDefaultColorTransform();
+        }
+    }
+
+    /**
+     * Gets the supported color transforms of this device.
+     * @hide
+     */
+    public ColorTransform[] getSupportedColorTransforms() {
+        synchronized (this) {
+            updateDisplayInfoLocked();
+            ColorTransform[] transforms = mDisplayInfo.supportedColorTransforms;
+            return Arrays.copyOf(transforms, transforms.length);
+        }
+    }
+
+    /**
      * Gets the app VSYNC offset, in nanoseconds.  This is a positive value indicating
      * the phase offset of the VSYNC events provided by Choreographer relative to the
      * display refresh.  For example, if Choreographer reports that the refresh occurred
@@ -1054,4 +1102,89 @@
             }
         };
     }
+
+    /**
+     * A color transform supported by a given display.
+     *
+     * @see Display#getSupportedColorTransforms()
+     * @hide
+     */
+    public static final class ColorTransform implements Parcelable {
+        public static final ColorTransform[] EMPTY_ARRAY = new ColorTransform[0];
+
+        private final int mId;
+        private final int mColorTransform;
+
+        public ColorTransform(int id, int colorTransform) {
+            mId = id;
+            mColorTransform = colorTransform;
+        }
+
+        public int getId() {
+            return mId;
+        }
+
+        public int getColorTransform() {
+            return mColorTransform;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof ColorTransform)) {
+                return false;
+            }
+            ColorTransform that = (ColorTransform) other;
+            return mId == that.mId
+                && mColorTransform == that.mColorTransform;
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = 1;
+            hash = hash * 17 + mId;
+            hash = hash * 17 + mColorTransform;
+            return hash;
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder("{")
+                    .append("id=").append(mId)
+                    .append(", colorTransform=").append(mColorTransform)
+                    .append("}")
+                    .toString();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        private ColorTransform(Parcel in) {
+            this(in.readInt(), in.readInt());
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int parcelableFlags) {
+            out.writeInt(mId);
+            out.writeInt(mColorTransform);
+        }
+
+        @SuppressWarnings("hiding")
+        public static final Parcelable.Creator<ColorTransform> CREATOR
+                = new Parcelable.Creator<ColorTransform>() {
+            @Override
+            public ColorTransform createFromParcel(Parcel in) {
+                return new ColorTransform(in);
+            }
+
+            @Override
+            public ColorTransform[] newArray(int size) {
+                return new ColorTransform[size];
+            }
+        };
+    }
 }
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index cf17990..ee76274 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -169,6 +169,15 @@
      */
     public Display.Mode[] supportedModes = Display.Mode.EMPTY_ARRAY;
 
+    /** The active color transform. */
+    public int colorTransformId;
+
+    /** The default color transform. */
+    public int defaultColorTransformId;
+
+    /** The list of supported color transforms */
+    public Display.ColorTransform[] supportedColorTransforms = Display.ColorTransform.EMPTY_ARRAY;
+
     /**
      * The logical display density which is the basis for density-independent
      * pixels.
@@ -279,6 +288,8 @@
                 && rotation == other.rotation
                 && modeId == other.modeId
                 && defaultModeId == other.defaultModeId
+                && colorTransformId == other.colorTransformId
+                && defaultColorTransformId == other.defaultColorTransformId
                 && logicalDensityDpi == other.logicalDensityDpi
                 && physicalXDpi == other.physicalXDpi
                 && physicalYDpi == other.physicalYDpi
@@ -317,6 +328,10 @@
         modeId = other.modeId;
         defaultModeId = other.defaultModeId;
         supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length);
+        colorTransformId = other.colorTransformId;
+        defaultColorTransformId = other.defaultColorTransformId;
+        supportedColorTransforms = Arrays.copyOf(
+                other.supportedColorTransforms, other.supportedColorTransforms.length);
         logicalDensityDpi = other.logicalDensityDpi;
         physicalXDpi = other.physicalXDpi;
         physicalYDpi = other.physicalYDpi;
@@ -353,6 +368,13 @@
         for (int i = 0; i < nModes; i++) {
             supportedModes[i] = Display.Mode.CREATOR.createFromParcel(source);
         }
+        colorTransformId = source.readInt();
+        defaultColorTransformId = source.readInt();
+        int nColorTransforms = source.readInt();
+        supportedColorTransforms = new Display.ColorTransform[nColorTransforms];
+        for (int i = 0; i < nColorTransforms; i++) {
+            supportedColorTransforms[i] = Display.ColorTransform.CREATOR.createFromParcel(source);
+        }
         logicalDensityDpi = source.readInt();
         physicalXDpi = source.readFloat();
         physicalYDpi = source.readFloat();
@@ -390,6 +412,12 @@
         for (int i = 0; i < supportedModes.length; i++) {
             supportedModes[i].writeToParcel(dest, flags);
         }
+        dest.writeInt(colorTransformId);
+        dest.writeInt(defaultColorTransformId);
+        dest.writeInt(supportedColorTransforms.length);
+        for (int i = 0; i < supportedColorTransforms.length; i++) {
+            supportedColorTransforms[i].writeToParcel(dest, flags);
+        }
         dest.writeInt(logicalDensityDpi);
         dest.writeFloat(physicalXDpi);
         dest.writeFloat(physicalYDpi);
@@ -461,6 +489,24 @@
         return result;
     }
 
+    public Display.ColorTransform getColorTransform() {
+        return findColorTransform(colorTransformId);
+    }
+
+    public Display.ColorTransform getDefaultColorTransform() {
+        return findColorTransform(defaultColorTransformId);
+    }
+
+    private Display.ColorTransform findColorTransform(int colorTransformId) {
+        for (int i = 0; i < supportedColorTransforms.length; i++) {
+            Display.ColorTransform colorTransform = supportedColorTransforms[i];
+            if (colorTransform.getId() == colorTransformId) {
+                return colorTransform;
+            }
+        }
+        throw new IllegalStateException("Unable to locate color transform: " + colorTransformId);
+    }
+
     public void getAppMetrics(DisplayMetrics outMetrics) {
         getAppMetrics(outMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
     }
@@ -562,6 +608,12 @@
         sb.append(defaultModeId);
         sb.append(", modes ");
         sb.append(Arrays.toString(supportedModes));
+        sb.append(", colorTransformId ");
+        sb.append(colorTransformId);
+        sb.append(", defaultColorTransformId ");
+        sb.append(defaultColorTransformId);
+        sb.append(", supportedColorTransforms ");
+        sb.append(Arrays.toString(supportedColorTransforms));
         sb.append(", rotation ");
         sb.append(rotation);
         sb.append(", density ");
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 420f7a1..dcef142 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -108,6 +108,11 @@
     private Choreographer mChoreographer;
     private boolean mRootNodeNeedsUpdate;
 
+    // In case of multi threaded render nodes, these bounds indicate the content bounds against
+    // which the backdrop needs to be cropped against.
+    private final Rect mCurrentContentBounds = new Rect();
+    private final Rect mStagedContentBounds = new Rect();
+
     ThreadedRenderer(Context context, boolean translucent) {
         final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
         mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
@@ -307,6 +312,47 @@
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
 
+    /**
+     * Adds a rendernode to the renderer which can be drawn and changed asynchronously to the
+     * rendernode of the UI thread.
+     * @param node The node to add.
+     * @param placeFront If true, the render node will be placed in front of the content node,
+     *                   otherwise behind the content node.
+     */
+    public void addRenderNode(RenderNode node, boolean placeFront) {
+        nAddRenderNode(mNativeProxy, node.mNativeRenderNode, placeFront);
+    }
+
+    /**
+     * Only especially added render nodes can be removed.
+     * @param node The node which was added via addRenderNode which should get removed again.
+     */
+    public void removeRenderNode(RenderNode node) {
+        nRemoveRenderNode(mNativeProxy, node.mNativeRenderNode);
+    }
+
+    /**
+     * Draws a particular render node. If the node is not the content node, only the additional
+     * nodes will get drawn and the content remains untouched.
+     * @param node The node to be drawn.
+     */
+    public void drawRenderNode(RenderNode node) {
+        nDrawRenderNode(mNativeProxy, node.mNativeRenderNode);
+    }
+
+    /**
+     * To avoid unnecessary overdrawing of the main content all additionally passed render nodes
+     * will be prevented to overdraw this area. It will be synchronized with the draw call.
+     * This should be updated in the content view's draw call.
+     * @param left The left side of the protected bounds.
+     * @param top The top side of the protected bounds.
+     * @param right The right side of the protected bounds.
+     * @param bottom The bottom side of the protected bounds.
+     */
+    public void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) {
+        mStagedContentBounds.set(left, top, right, bottom);
+    }
+
     @Override
     void invalidateRoot() {
         mRootNodeNeedsUpdate = true;
@@ -320,6 +366,14 @@
         choreographer.mFrameInfo.markDrawStart();
 
         updateRootDisplayList(view, callbacks);
+        // The main content view was updating the content bounds and we transfer them to the
+        // renderer.
+        if (!mCurrentContentBounds.equals(mStagedContentBounds)) {
+            mCurrentContentBounds.set(mStagedContentBounds);
+            nSetContentOverdrawProtectionBounds(mNativeProxy, mCurrentContentBounds.left,
+                mCurrentContentBounds.top, mCurrentContentBounds.right,
+            mCurrentContentBounds.bottom);
+        }
 
         attachInfo.mIgnoreDirtyState = false;
 
@@ -541,4 +595,11 @@
     private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd,
             @DumpFlags int dumpFlags);
     private static native void nDumpProfileData(byte[] data, FileDescriptor fd);
+
+    private static native void nAddRenderNode(long nativeProxy, long rootRenderNode,
+             boolean placeFront);
+    private static native void nRemoveRenderNode(long nativeProxy, long rootRenderNode);
+    private static native void nDrawRenderNode(long nativeProxy, long rootRenderNode);
+    private static native void nSetContentOverdrawProtectionBounds(long nativeProxy, int left,
+             int top, int right, int bottom);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 98c0494..2a1d757 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4706,7 +4706,7 @@
             out.append(" #");
             out.append(Integer.toHexString(id));
             final Resources r = mResources;
-            if (Resources.resourceHasPackage(id) && r != null) {
+            if (id > 0 && Resources.resourceHasPackage(id) && r != null) {
                 try {
                     String pkgname;
                     switch (id&0xff000000) {
@@ -7373,6 +7373,23 @@
     }
 
     /**
+     * Compute the view's coordinate within the surface.
+     *
+     * <p>Computes the coordinates of this view in its surface. The argument
+     * must be an array of two integers. After the method returns, the array
+     * contains the x and y location in that order.</p>
+     * @hide
+     * @param location an array of two integers in which to hold the coordinates
+     */
+    public void getLocationInSurface(@Size(2) int[] location) {
+        getLocationInWindow(location);
+        if (mAttachInfo != null && mAttachInfo.mViewRootImpl != null) {
+            location[0] += mAttachInfo.mViewRootImpl.mWindowAttributes.surfaceInsets.left;
+            location[1] += mAttachInfo.mViewRootImpl.mWindowAttributes.surfaceInsets.top;
+        }
+    }
+
+    /**
      * Provide original WindowInsets that are dispatched to the view hierarchy. The insets are
      * only available if the view is attached.
      *
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2ddf8ad..8403c46 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -169,6 +169,16 @@
     boolean mAppVisible = true;
     int mOrigWindowType = -1;
 
+    /** Whether the window had focus during the most recent traversal. */
+    boolean mHadWindowFocus;
+
+    /**
+     * Whether the window lost focus during a previous traversal and has not
+     * yet gained it back. Used to determine whether a WINDOW_STATE_CHANGE
+     * accessibility events should be sent during traversal.
+     */
+    boolean mLostWindowFocus;
+
     // Set to true if the owner of this window is in the stopped state,
     // so the window should no longer be active.
     boolean mStopped = false;
@@ -1522,10 +1532,11 @@
         boolean insetsPending = false;
         int relayoutResult = 0;
 
+        boolean isViewVisible = viewVisibility == View.VISIBLE;
         if (mFirst || windowShouldResize || insetsChanged ||
                 viewVisibilityChanged || params != null) {
 
-            if (viewVisibility == View.VISIBLE) {
+            if (isViewVisible) {
                 // If this window is giving internal insets to the window
                 // manager, and it is being added or changing its visibility,
                 // then we want to first give the window manager "fake"
@@ -1776,10 +1787,6 @@
                         || mHeight != hardwareRenderer.getHeight()) {
                     hardwareRenderer.setup(mWidth, mHeight, mAttachInfo,
                             mWindowAttributes.surfaceInsets);
-                    if (!hwInitialized) {
-                        hardwareRenderer.invalidate(mSurface);
-                        mFullRedrawNeeded = true;
-                    }
                 }
             }
 
@@ -1964,19 +1971,33 @@
             mRemainingFrameCount--;
         }
 
+        final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
+        final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
+        final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
+        if (regainedFocus) {
+            mLostWindowFocus = false;
+        } else if (!hasWindowFocus && mHadWindowFocus) {
+            mLostWindowFocus = true;
+        }
+
+        if (changedVisibility || regainedFocus) {
+            host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        }
+
         mFirst = false;
         mWillDrawSoon = false;
         mNewSurfaceNeeded = false;
         mViewVisibility = viewVisibility;
+        mHadWindowFocus = hasWindowFocus;
 
-        if (mAttachInfo.mHasWindowFocus && !isInLocalFocusMode()) {
+        if (hasWindowFocus && !isInLocalFocusMode()) {
             final boolean imTarget = WindowManager.LayoutParams
                     .mayUseInputMethod(mWindowAttributes.flags);
             if (imTarget != mLastWasImTarget) {
                 mLastWasImTarget = imTarget;
                 InputMethodManager imm = InputMethodManager.peekInstance();
                 if (imm != null && imTarget) {
-                    imm.onPreWindowFocus(mView, true /* hasWindowFocus */);
+                    imm.onPreWindowFocus(mView, hasWindowFocus);
                     imm.onPostWindowFocus(mView, mView.findFocus(),
                             mWindowAttributes.softInputMode,
                             !mHasHadWindowFocus, mWindowAttributes.flags);
@@ -1989,8 +2010,7 @@
             mReportNextDraw = true;
         }
 
-        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() ||
-                viewVisibility != View.VISIBLE;
+        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
 
         if (!cancelDraw && !newSurface) {
             if (!skipDraw || mReportNextDraw) {
@@ -2004,7 +2024,7 @@
                 performDraw();
             }
         } else {
-            if (viewVisibility == View.VISIBLE) {
+            if (isViewVisible) {
                 // Try again
                 scheduleTraversals();
             } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
@@ -3313,13 +3333,6 @@
                                     ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
                         mHasHadWindowFocus = true;
                     }
-
-                    if (mView != null && mAccessibilityManager.isEnabled()) {
-                        if (hasWindowFocus) {
-                            mView.sendAccessibilityEvent(
-                                    AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-                        }
-                    }
                 }
             } break;
             case MSG_DIE:
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 5b19edf..2172b5c 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -647,7 +647,8 @@
 
         @Override
         public CharSequence getExtendedInfo() {
-            return mSourceInfo != null ? mSourceInfo.getExtendedInfo() : null;
+            // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
+            return null;
         }
 
         @Override
@@ -740,9 +741,8 @@
 
         @Override
         public boolean showsExtendedInfo(TargetInfo info) {
-            // Reserve space to show extended info if any one of the items in the adapter has
-            // extended info. This keeps grid item sizes uniform.
-            return hasExtendedInfo();
+            // We have badges so we don't need this text shown.
+            return false;
         }
 
         @Override
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 9272193..ef9d1ce 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -896,6 +896,7 @@
         private final ResolveInfo mResolveInfo;
         private final CharSequence mDisplayLabel;
         private Drawable mDisplayIcon;
+        private Drawable mBadge;
         private final CharSequence mExtendedInfo;
         private final Intent mResolvedIntent;
         private final List<Intent> mSourceIntents = new ArrayList<>();
@@ -940,7 +941,25 @@
         }
 
         public Drawable getBadgeIcon() {
-            return null;
+            // We only expose a badge if we have extended info.
+            // The badge is a higher-priority disambiguation signal
+            // but we don't need one if we wouldn't show extended info at all.
+            if (TextUtils.isEmpty(getExtendedInfo())) {
+                return null;
+            }
+
+            if (mBadge == null && mResolveInfo != null && mResolveInfo.activityInfo != null
+                    && mResolveInfo.activityInfo.applicationInfo != null) {
+                if (mResolveInfo.activityInfo.icon == 0 || mResolveInfo.activityInfo.icon
+                        == mResolveInfo.activityInfo.applicationInfo.icon) {
+                    // Badging an icon with exactly the same icon is silly.
+                    // If the activityInfo icon resid is 0 it will fall back
+                    // to the application's icon, making it a match.
+                    return null;
+                }
+                mBadge = mResolveInfo.activityInfo.applicationInfo.loadIcon(mPm);
+            }
+            return mBadge;
         }
 
         @Override
@@ -1378,8 +1397,8 @@
             } else {
                 mHasExtendedInfo = true;
                 boolean usePkg = false;
-                CharSequence startApp = ro.getResolveInfoAt(0).activityInfo.applicationInfo
-                        .loadLabel(mPm);
+                final ApplicationInfo ai = ro.getResolveInfoAt(0).activityInfo.applicationInfo;
+                final CharSequence startApp = ai.loadLabel(mPm);
                 if (startApp == null) {
                     usePkg = true;
                 }
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index 4f4d3e0..f178c8c 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -338,7 +338,7 @@
         }
 
         if (mCpuPowerCalculator == null) {
-            mCpuPowerCalculator = new CpuPowerCalculator();
+            mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
         }
         mCpuPowerCalculator.reset();
 
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 4ff7869..6ccdd08 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -105,7 +105,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 130 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 131 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -118,8 +118,6 @@
     // in to one common name.
     private static final int MAX_WAKELOCKS_PER_UID = 100;
 
-    private static int sNumSpeedSteps;
-
     private final JournaledFile mFile;
     public final AtomicFile mCheckinFile;
     public final AtomicFile mDailyFile;
@@ -133,7 +131,7 @@
     private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
 
     private final KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
-    private final KernelCpuSpeedReader mKernelCpuSpeedReader = new KernelCpuSpeedReader();
+    private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
 
     public interface BatteryCallback {
         public void batteryNeedsCpuUpdate();
@@ -4411,7 +4409,7 @@
         LongSamplingCounter mUserCpuTime = new LongSamplingCounter(mOnBatteryTimeBase);
         LongSamplingCounter mSystemCpuTime = new LongSamplingCounter(mOnBatteryTimeBase);
         LongSamplingCounter mCpuPower = new LongSamplingCounter(mOnBatteryTimeBase);
-        LongSamplingCounter[] mSpeedBins;
+        LongSamplingCounter[][] mCpuClusterSpeed;
 
         /**
          * The statistics we have collected for this uid's wake locks.
@@ -4470,7 +4468,6 @@
             mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
                     mWifiMulticastTimers, mOnBatteryTimeBase);
             mProcessStateTimer = new StopwatchTimer[NUM_PROCESS_STATE];
-            mSpeedBins = new LongSamplingCounter[getCpuSpeedSteps()];
         }
 
         @Override
@@ -5008,10 +5005,18 @@
         }
 
         @Override
-        public long getTimeAtCpuSpeed(int step, int which) {
-            if (step >= 0 && step < mSpeedBins.length) {
-                if (mSpeedBins[step] != null) {
-                    return mSpeedBins[step].getCountLocked(which);
+        public long getTimeAtCpuSpeed(int cluster, int step, int which) {
+            if (mCpuClusterSpeed != null) {
+                if (cluster >= 0 && cluster < mCpuClusterSpeed.length) {
+                    final LongSamplingCounter[] cpuSpeeds = mCpuClusterSpeed[cluster];
+                    if (cpuSpeeds != null) {
+                        if (step >= 0 && step < cpuSpeeds.length) {
+                            final LongSamplingCounter c = cpuSpeeds[step];
+                            if (c != null) {
+                                return c.getCountLocked(which);
+                            }
+                        }
+                    }
                 }
             }
             return 0;
@@ -5128,10 +5133,16 @@
             mUserCpuTime.reset(false);
             mSystemCpuTime.reset(false);
             mCpuPower.reset(false);
-            for (int i = 0; i < mSpeedBins.length; i++) {
-                LongSamplingCounter c = mSpeedBins[i];
-                if (c != null) {
-                    c.reset(false);
+
+            if (mCpuClusterSpeed != null) {
+                for (LongSamplingCounter[] speeds : mCpuClusterSpeed) {
+                    if (speeds != null) {
+                        for (LongSamplingCounter speed : speeds) {
+                            if (speed != null) {
+                                speed.reset(false);
+                            }
+                        }
+                    }
                 }
             }
 
@@ -5280,10 +5291,16 @@
                 mUserCpuTime.detach();
                 mSystemCpuTime.detach();
                 mCpuPower.detach();
-                for (int i = 0; i < mSpeedBins.length; i++) {
-                    LongSamplingCounter c = mSpeedBins[i];
-                    if (c != null) {
-                        c.detach();
+
+                if (mCpuClusterSpeed != null) {
+                    for (LongSamplingCounter[] cpuSpeeds : mCpuClusterSpeed) {
+                        if (cpuSpeeds != null) {
+                            for (LongSamplingCounter c : cpuSpeeds) {
+                                if (c != null) {
+                                    c.detach();
+                                }
+                            }
+                        }
                     }
                 }
             }
@@ -5461,15 +5478,27 @@
             mSystemCpuTime.writeToParcel(out);
             mCpuPower.writeToParcel(out);
 
-            out.writeInt(mSpeedBins.length);
-            for (int i = 0; i < mSpeedBins.length; i++) {
-                LongSamplingCounter c = mSpeedBins[i];
-                if (c != null) {
-                    out.writeInt(1);
-                    c.writeToParcel(out);
-                } else {
-                    out.writeInt(0);
+            if (mCpuClusterSpeed != null) {
+                out.writeInt(1);
+                out.writeInt(mCpuClusterSpeed.length);
+                for (LongSamplingCounter[] cpuSpeeds : mCpuClusterSpeed) {
+                    if (cpuSpeeds != null) {
+                        out.writeInt(1);
+                        out.writeInt(cpuSpeeds.length);
+                        for (LongSamplingCounter c : cpuSpeeds) {
+                            if (c != null) {
+                                out.writeInt(1);
+                                c.writeToParcel(out);
+                            } else {
+                                out.writeInt(0);
+                            }
+                        }
+                    } else {
+                        out.writeInt(0);
+                    }
                 }
+            } else {
+                out.writeInt(0);
             }
         }
 
@@ -5653,13 +5682,32 @@
             mSystemCpuTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
             mCpuPower = new LongSamplingCounter(mOnBatteryTimeBase, in);
 
-            int bins = in.readInt();
-            int steps = getCpuSpeedSteps();
-            mSpeedBins = new LongSamplingCounter[bins >= steps ? bins : steps];
-            for (int i = 0; i < bins; i++) {
-                if (in.readInt() != 0) {
-                    mSpeedBins[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+            if (in.readInt() != 0) {
+                int numCpuClusters = in.readInt();
+                if (mPowerProfile != null && mPowerProfile.getNumCpuClusters() != numCpuClusters) {
+                    throw new ParcelFormatException("Incompatible number of cpu clusters");
                 }
+
+                mCpuClusterSpeed = new LongSamplingCounter[numCpuClusters][];
+                for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+                    if (in.readInt() != 0) {
+                        int numSpeeds = in.readInt();
+                        if (mPowerProfile != null &&
+                                mPowerProfile.getNumSpeedStepsInCpuCluster(cluster) != numSpeeds) {
+                            throw new ParcelFormatException("Incompatible number of cpu speeds");
+                        }
+
+                        final LongSamplingCounter[] cpuSpeeds = new LongSamplingCounter[numSpeeds];
+                        mCpuClusterSpeed[cluster] = cpuSpeeds;
+                        for (int speed = 0; speed < numSpeeds; speed++) {
+                            if (in.readInt() != 0) {
+                                cpuSpeeds[speed] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+                            }
+                        }
+                    }
+                }
+            } else {
+                mCpuClusterSpeed = null;
             }
         }
 
@@ -6874,6 +6922,19 @@
     public void setPowerProfile(PowerProfile profile) {
         synchronized (this) {
             mPowerProfile = profile;
+
+            // We need to initialize the KernelCpuSpeedReaders to read from
+            // the first cpu of each core. Once we have the PowerProfile, we have access to this
+            // information.
+            final int numClusters = mPowerProfile.getNumCpuClusters();
+            mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
+            int firstCpuOfCluster = 0;
+            for (int i = 0; i < numClusters; i++) {
+                final int numSpeedSteps = mPowerProfile.getNumSpeedStepsInCpuCluster(i);
+                mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
+                        numSpeedSteps);
+                firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
+            }
         }
     }
 
@@ -6881,10 +6942,6 @@
         mCallback = cb;
     }
 
-    public void setNumSpeedSteps(int steps) {
-        if (sNumSpeedSteps == 0) sNumSpeedSteps = steps;
-    }
-
     public void setRadioScanningTimeout(long timeout) {
         if (mPhoneSignalScanningTimer != null) {
             mPhoneSignalScanningTimer.setTimeout(timeout);
@@ -7997,9 +8054,11 @@
         // If no app is holding a wakelock, then the distribution is normal.
         final int wakelockWeight = 50;
 
-        // Read the time spent at various cpu frequencies.
-        final int cpuSpeedSteps = getCpuSpeedSteps();
-        final long[] cpuSpeeds = mKernelCpuSpeedReader.readDelta();
+        // Read the time spent for each cluster at various cpu frequencies.
+        final long[][] clusterSpeeds = new long[mKernelCpuSpeedReaders.length][];
+        for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
+            clusterSpeeds[cluster] = mKernelCpuSpeedReaders[cluster].readDelta();
+        }
 
         int numWakelocks = 0;
 
@@ -8072,11 +8131,23 @@
 
                         // Add the cpu speeds to this UID. These are used as a ratio
                         // for computing the power this UID used.
-                        for (int i = 0; i < cpuSpeedSteps; i++) {
-                            if (u.mSpeedBins[i] == null) {
-                                u.mSpeedBins[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+                        if (u.mCpuClusterSpeed == null) {
+                            u.mCpuClusterSpeed = new LongSamplingCounter[clusterSpeeds.length][];
+                        }
+
+                        for (int cluster = 0; cluster < clusterSpeeds.length; cluster++) {
+                            if (u.mCpuClusterSpeed[cluster] == null) {
+                                u.mCpuClusterSpeed[cluster] =
+                                        new LongSamplingCounter[clusterSpeeds[cluster].length];
                             }
-                            u.mSpeedBins[i].addCountLocked(cpuSpeeds[i]);
+
+                            final LongSamplingCounter[] cpuSpeeds = u.mCpuClusterSpeed[cluster];
+                            for (int speed = 0; speed < clusterSpeeds[cluster].length; speed++) {
+                                if (cpuSpeeds[speed] == null) {
+                                    cpuSpeeds[speed] = new LongSamplingCounter(mOnBatteryTimeBase);
+                                }
+                                cpuSpeeds[speed].addCountLocked(clusterSpeeds[cluster][speed]);
+                            }
                         }
                     }
                 });
@@ -8776,11 +8847,6 @@
         }
     }
 
-    @Override
-    public int getCpuSpeedSteps() {
-        return sNumSpeedSteps;
-    }
-
     /**
      * Retrieve the statistics object for a particular uid, creating if needed.
      */
@@ -9216,11 +9282,6 @@
             }
         }
 
-        sNumSpeedSteps = in.readInt();
-        if (sNumSpeedSteps < 0 || sNumSpeedSteps > 100) {
-            throw new ParcelFormatException("Bad speed steps in data: " + sNumSpeedSteps);
-        }
-
         final int NU = in.readInt();
         if (NU > 10000) {
             throw new ParcelFormatException("File corrupt: too many uids " + NU);
@@ -9304,17 +9365,33 @@
             u.mSystemCpuTime.readSummaryFromParcelLocked(in);
             u.mCpuPower.readSummaryFromParcelLocked(in);
 
-            int NSB = in.readInt();
-            if (NSB > 100) {
-                throw new ParcelFormatException("File corrupt: too many speed bins " + NSB);
-            }
-
-            u.mSpeedBins = new LongSamplingCounter[NSB];
-            for (int i=0; i<NSB; i++) {
-                if (in.readInt() != 0) {
-                    u.mSpeedBins[i] = new LongSamplingCounter(mOnBatteryTimeBase);
-                    u.mSpeedBins[i].readSummaryFromParcelLocked(in);
+            if (in.readInt() != 0) {
+                final int numClusters = in.readInt();
+                if (mPowerProfile != null && mPowerProfile.getNumCpuClusters() != numClusters) {
+                    throw new ParcelFormatException("Incompatible cpu cluster arrangement");
                 }
+
+                u.mCpuClusterSpeed = new LongSamplingCounter[numClusters][];
+                for (int cluster = 0; cluster < numClusters; cluster++) {
+                    int NSB = in.readInt();
+                    if (mPowerProfile != null &&
+                            mPowerProfile.getNumSpeedStepsInCpuCluster(cluster) != NSB) {
+                        throw new ParcelFormatException("File corrupt: too many speed bins " + NSB);
+                    }
+
+                    if (in.readInt() != 0) {
+                        u.mCpuClusterSpeed[cluster] = new LongSamplingCounter[NSB];
+                        for (int speed = 0; speed < NSB; speed++) {
+                            if (in.readInt() != 0) {
+                                u.mCpuClusterSpeed[cluster][speed] = new LongSamplingCounter(
+                                        mOnBatteryTimeBase);
+                                u.mCpuClusterSpeed[cluster][speed].readSummaryFromParcelLocked(in);
+                            }
+                        }
+                    }
+                }
+            } else {
+                u.mCpuClusterSpeed = null;
             }
 
             int NW = in.readInt();
@@ -9531,7 +9608,6 @@
             }
         }
 
-        out.writeInt(sNumSpeedSteps);
         final int NU = mUidStats.size();
         out.writeInt(NU);
         for (int iu = 0; iu < NU; iu++) {
@@ -9640,15 +9716,27 @@
             u.mSystemCpuTime.writeSummaryFromParcelLocked(out);
             u.mCpuPower.writeSummaryFromParcelLocked(out);
 
-            out.writeInt(u.mSpeedBins.length);
-            for (int i = 0; i < u.mSpeedBins.length; i++) {
-                LongSamplingCounter speedBin = u.mSpeedBins[i];
-                if (speedBin != null) {
-                    out.writeInt(1);
-                    speedBin.writeSummaryFromParcelLocked(out);
-                } else {
-                    out.writeInt(0);
+            if (u.mCpuClusterSpeed != null) {
+                out.writeInt(1);
+                out.writeInt(u.mCpuClusterSpeed.length);
+                for (LongSamplingCounter[] cpuSpeeds : u.mCpuClusterSpeed) {
+                    if (cpuSpeeds != null) {
+                        out.writeInt(1);
+                        out.writeInt(cpuSpeeds.length);
+                        for (LongSamplingCounter c : cpuSpeeds) {
+                            if (c != null) {
+                                out.writeInt(1);
+                                c.writeSummaryFromParcelLocked(out);
+                            } else {
+                                out.writeInt(0);
+                            }
+                        }
+                    } else {
+                        out.writeInt(0);
+                    }
                 }
+            } else {
+                out.writeInt(0);
             }
 
             final ArrayMap<String, Uid.Wakelock> wakeStats = u.mWakelockStats.getMap();
@@ -9897,8 +9985,6 @@
         mFlashlightTurnedOnTimers.clear();
         mCameraTurnedOnTimers.clear();
 
-        sNumSpeedSteps = in.readInt();
-
         int numUids = in.readInt();
         mUidStats.clear();
         for (int i = 0; i < numUids; i++) {
@@ -10037,8 +10123,6 @@
             out.writeInt(0);
         }
 
-        out.writeInt(sNumSpeedSteps);
-
         if (inclUids) {
             int size = mUidStats.size();
             out.writeInt(size);
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index d62f7a6..8417856 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -22,12 +22,47 @@
 public class CpuPowerCalculator extends PowerCalculator {
     private static final String TAG = "CpuPowerCalculator";
     private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+    private final PowerProfile mProfile;
+
+    public CpuPowerCalculator(PowerProfile profile) {
+        mProfile = profile;
+    }
 
     @Override
     public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                              long rawUptimeUs, int statsType) {
+
         app.cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
-        app.cpuPowerMah = (double) u.getCpuPowerMaUs(statsType) / (60.0 * 60.0 * 1000.0 * 1000.0);
+
+        // Aggregate total time spent on each cluster.
+        long totalTime = 0;
+        final int numClusters = mProfile.getNumCpuClusters();
+        for (int cluster = 0; cluster < numClusters; cluster++) {
+            final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
+            for (int speed = 0; speed < speedsForCluster; speed++) {
+                totalTime += u.getTimeAtCpuSpeed(cluster, speed, statsType);
+            }
+        }
+        totalTime = Math.max(totalTime, 1);
+
+        double cpuPowerMaMs = 0;
+        for (int cluster = 0; cluster < numClusters; cluster++) {
+            final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
+            for (int speed = 0; speed < speedsForCluster; speed++) {
+                final double ratio = (double) u.getTimeAtCpuSpeed(cluster, speed, statsType) /
+                        totalTime;
+                final double cpuSpeedStepPower = ratio * app.cpuTimeMs *
+                        mProfile.getAveragePowerForCpu(cluster, speed);
+                if (DEBUG && ratio != 0) {
+                    Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
+                            + speed + " ratio=" + BatteryStatsHelper.makemAh(ratio) + " power="
+                            + BatteryStatsHelper.makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
+                }
+                cpuPowerMaMs += cpuSpeedStepPower;
+            }
+        }
+        app.cpuPowerMah = cpuPowerMaMs / (60 * 60 * 1000);
+
         if (DEBUG && (app.cpuTimeMs != 0 || app.cpuPowerMah != 0)) {
             Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + app.cpuTimeMs + " ms power="
                     + BatteryStatsHelper.makemAh(app.cpuPowerMah));
diff --git a/core/java/com/android/internal/os/KernelCpuSpeedReader.java b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
index c30df28..5b776ac 100644
--- a/core/java/com/android/internal/os/KernelCpuSpeedReader.java
+++ b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
@@ -24,8 +24,8 @@
 import java.util.Arrays;
 
 /**
- * Reads CPU time spent at various frequencies and provides a delta from the last call to
- * {@link #readDelta}. Each line in the proc file has the format:
+ * Reads CPU time of a specific core spent at various frequencies and provides a delta from the
+ * last call to {@link #readDelta}. Each line in the proc file has the format:
  *
  * freq time
  *
@@ -33,12 +33,20 @@
  */
 public class KernelCpuSpeedReader {
     private static final String TAG = "KernelCpuSpeedReader";
-    private static final String sProcFile =
-            "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state";
-    private static final int MAX_SPEEDS = 60;
 
-    private long[] mLastSpeedTimes = new long[MAX_SPEEDS];
-    private long[] mDeltaSpeedTimes = new long[MAX_SPEEDS];
+    private final String mProcFile;
+    private final long[] mLastSpeedTimes;
+    private final long[] mDeltaSpeedTimes;
+
+    /**
+     * @param cpuNumber The cpu (cpu0, cpu1, etc) whose state to read.
+     */
+    public KernelCpuSpeedReader(int cpuNumber, int numSpeedSteps) {
+        mProcFile = String.format("/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state",
+                cpuNumber);
+        mLastSpeedTimes = new long[numSpeedSteps];
+        mDeltaSpeedTimes = new long[numSpeedSteps];
+    }
 
     /**
      * The returned array is modified in subsequent calls to {@link #readDelta}.
@@ -46,22 +54,28 @@
      * {@link #readDelta}.
      */
     public long[] readDelta() {
-        try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
+        try (BufferedReader reader = new BufferedReader(new FileReader(mProcFile))) {
             TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
             String line;
             int speedIndex = 0;
-            while ((line = reader.readLine()) != null) {
+            while (speedIndex < mLastSpeedTimes.length && (line = reader.readLine()) != null) {
                 splitter.setString(line);
                 Long.parseLong(splitter.next());
 
                 // The proc file reports time in 1/100 sec, so convert to milliseconds.
                 long time = Long.parseLong(splitter.next()) * 10;
-                mDeltaSpeedTimes[speedIndex] = time - mLastSpeedTimes[speedIndex];
+                if (time < mLastSpeedTimes[speedIndex]) {
+                    // The stats reset when the cpu hotplugged. That means that the time
+                    // we read is offset from 0, so the time is the delta.
+                    mDeltaSpeedTimes[speedIndex] = time;
+                } else {
+                    mDeltaSpeedTimes[speedIndex] = time - mLastSpeedTimes[speedIndex];
+                }
                 mLastSpeedTimes[speedIndex] = time;
                 speedIndex++;
             }
         } catch (IOException e) {
-            Slog.e(TAG, "Failed to read cpu-freq", e);
+            Slog.e(TAG, "Failed to read cpu-freq: " + e.getMessage());
             Arrays.fill(mDeltaSpeedTimes, 0);
         }
         return mDeltaSpeedTimes;
diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
index 0df78ed..5d3043c 100644
--- a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
@@ -137,7 +137,7 @@
                 mLastPowerMaUs.put(uid, powerMaUs);
             }
         } catch (IOException e) {
-            Slog.e(TAG, "Failed to read uid_cputime", e);
+            Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage());
         }
         mLastTimeReadUs = nowUs;
     }
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 4ede8dda..aaa9f73 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -59,6 +59,7 @@
     /**
      * Power consumption when CPU is in power collapse mode.
      */
+    @Deprecated
     public static final String POWER_CPU_ACTIVE = "cpu.active";
 
     /**
@@ -163,6 +164,7 @@
      */
     public static final String POWER_CAMERA = "camera.avg";
 
+    @Deprecated
     public static final String POWER_CPU_SPEEDS = "cpu.speeds";
 
     /**
@@ -191,6 +193,7 @@
         if (sPowerMap.size() == 0) {
             readPowerValuesFromXml(context);
         }
+        initCpuClusters();
     }
 
     private void readPowerValuesFromXml(Context context) {
@@ -249,7 +252,7 @@
         }
 
         // Now collect other config variables.
-        int[] configResIds = new int[] {
+        int[] configResIds = new int[]{
                 com.android.internal.R.integer.config_bluetooth_idle_cur_ma,
                 com.android.internal.R.integer.config_bluetooth_rx_cur_ma,
                 com.android.internal.R.integer.config_bluetooth_tx_cur_ma,
@@ -260,7 +263,7 @@
                 com.android.internal.R.integer.config_wifi_operating_voltage_mv,
         };
 
-        String[] configResIdKeys = new String[] {
+        String[] configResIdKeys = new String[]{
                 POWER_BLUETOOTH_CONTROLLER_IDLE,
                 POWER_BLUETOOTH_CONTROLLER_RX,
                 POWER_BLUETOOTH_CONTROLLER_TX,
@@ -279,6 +282,69 @@
         }
     }
 
+    private CpuClusterKey[] mCpuClusters;
+
+    private static final String POWER_CPU_CLUSTER_CORE_COUNT = "cpu.clusters.cores";
+    private static final String POWER_CPU_CLUSTER_SPEED_PREFIX = "cpu.speeds.cluster";
+    private static final String POWER_CPU_CLUSTER_ACTIVE_PREFIX = "cpu.active.cluster";
+
+    @SuppressWarnings("deprecated")
+    private void initCpuClusters() {
+        // Figure out how many CPU clusters we're dealing with
+        final Object obj = sPowerMap.get(POWER_CPU_CLUSTER_CORE_COUNT);
+        if (obj == null || !(obj instanceof Double[])) {
+            // Default to single.
+            mCpuClusters = new CpuClusterKey[1];
+            mCpuClusters[0] = new CpuClusterKey(POWER_CPU_SPEEDS, POWER_CPU_ACTIVE, 1);
+
+        } else {
+            final Double[] array = (Double[]) obj;
+            mCpuClusters = new CpuClusterKey[array.length];
+            for (int cluster = 0; cluster < array.length; cluster++) {
+                int numCpusInCluster = (int) Math.round(array[cluster]);
+                mCpuClusters[cluster] = new CpuClusterKey(
+                        POWER_CPU_CLUSTER_SPEED_PREFIX + cluster,
+                        POWER_CPU_CLUSTER_ACTIVE_PREFIX + cluster,
+                        numCpusInCluster);
+            }
+        }
+    }
+
+    public static class CpuClusterKey {
+        private final String timeKey;
+        private final String powerKey;
+        private final int numCpus;
+
+        private CpuClusterKey(String timeKey, String powerKey, int numCpus) {
+            this.timeKey = timeKey;
+            this.powerKey = powerKey;
+            this.numCpus = numCpus;
+        }
+    }
+
+    public int getNumCpuClusters() {
+        return mCpuClusters.length;
+    }
+
+    public int getNumCoresInCpuCluster(int index) {
+        return mCpuClusters[index].numCpus;
+    }
+
+    public int getNumSpeedStepsInCpuCluster(int index) {
+        Object value = sPowerMap.get(mCpuClusters[index].timeKey);
+        if (value != null && value instanceof Double[]) {
+            return ((Double[])value).length;
+        }
+        return 1; // Only one speed
+    }
+
+    public double getAveragePowerForCpu(int cluster, int step) {
+        if (cluster >= 0 && cluster < mCpuClusters.length) {
+            return getAveragePower(mCpuClusters[cluster].powerKey, step);
+        }
+        return 0;
+    }
+
     /**
      * Returns the average current in mA consumed by the subsystem, or the given
      * default value if the subsystem has no recorded value.
@@ -344,16 +410,4 @@
     public double getBatteryCapacity() {
         return getAveragePower(POWER_BATTERY_CAPACITY);
     }
-
-    /**
-     * Returns the number of speeds that the CPU can be run at.
-     * @return
-     */
-    public int getNumSpeedSteps() {
-        Object value = sPowerMap.get(POWER_CPU_SPEEDS);
-        if (value != null && value instanceof Double[]) {
-            return ((Double[])value).length;
-        }
-        return 1; // Only one speed
-    }
 }
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index 8535e6a..1bbbb089 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -47,7 +47,7 @@
         fHeight = height;
     }
     ~SkBitmapRegionDecoder() {
-        SkDELETE(fDecoder);
+        delete fDecoder;
     }
 
     bool decodeRegion(SkBitmap* bitmap, const SkIRect& rect,
@@ -72,7 +72,7 @@
     SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
     int width, height;
     if (NULL == decoder) {
-        SkDELETE(stream);
+        delete stream;
         doThrowIOE(env, "Image format not supported");
         return nullObjectReturn("SkImageDecoder::Factory returned null");
     }
@@ -87,7 +87,7 @@
         snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder",
                 decoder->getFormatName());
         doThrowIOE(env, msg);
-        SkDELETE(decoder);
+        delete decoder;
         return nullObjectReturn("decoder->buildTileIndex returned false");
     }
 
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 6c3676b..9a2703b 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -440,6 +440,32 @@
     }
 }
 
+static void android_view_ThreadedRenderer_addRenderNode(JNIEnv* env, jobject clazz,
+        jlong proxyPtr, jlong renderNodePtr, jboolean placeFront) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    proxy->addRenderNode(renderNode, placeFront);
+}
+
+static void android_view_ThreadedRenderer_removeRenderNode(JNIEnv* env, jobject clazz,
+        jlong proxyPtr, jlong renderNodePtr) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    proxy->removeRenderNode(renderNode);
+}
+
+static void android_view_ThreadedRendererd_drawRenderNode(JNIEnv* env, jobject clazz,
+        jlong proxyPtr, jlong renderNodePtr) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    proxy->drawRenderNode(renderNode);
+}
+
+static void android_view_ThreadedRenderer_setContentOverdrawProtectionBounds(JNIEnv* env,
+        jobject clazz, jlong proxyPtr, jint left, jint top, jint right, jint bottom) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    proxy->setContentOverdrawProtectionBounds(left, top, right, bottom);
+}
 
 // ----------------------------------------------------------------------------
 // Shaders
@@ -447,7 +473,6 @@
 
 static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,
         jstring diskCachePath) {
-
     const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL);
     egl_cache_t::get()->setCacheFilename(cacheArray);
     env->ReleaseStringUTFChars(diskCachePath, cacheArray);
@@ -494,6 +519,11 @@
     { "nDumpProfileData", "([BLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileData },
     { "setupShadersDiskCache", "(Ljava/lang/String;)V",
                 (void*) android_view_ThreadedRenderer_setupShadersDiskCache },
+    { "nAddRenderNode", "(JJZ)V", (void*) android_view_ThreadedRenderer_addRenderNode},
+    { "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
+    { "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
+    { "nSetContentOverdrawProtectionBounds", "(JIIII)V",
+                (void*)android_view_ThreadedRenderer_setContentOverdrawProtectionBounds},
 };
 
 int register_android_view_ThreadedRenderer(JNIEnv* env) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 921385d..664e621 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2175,6 +2175,13 @@
     <permission android:name="android.permission.CONTROL_WIFI_DISPLAY"
         android:protectionLevel="signature" />
 
+    <!-- Allows an application to control the color transforms applied to
+         displays system-wide.
+         <p>Not for use by third-party applications.</p>
+         @hide -->
+    <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_TRANSFORM"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to control VPN.
          <p>Not for use by third-party applications.</p>
          @hide -->
diff --git a/core/res/res/values-mcc240-mnc01/config.xml b/core/res/res/values-mcc240-mnc01/config.xml
new file mode 100644
index 0000000..7fb5c46
--- /dev/null
+++ b/core/res/res/values-mcc240-mnc01/config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2013, 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 my 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Do not set the system language as value of EF LI/EF PL -->
+    <bool name="config_use_sim_language_file">false</bool>
+
+</resources>
diff --git a/core/res/res/values-mcc240-mnc05/config.xml b/core/res/res/values-mcc240-mnc05/config.xml
new file mode 100644
index 0000000..7fb5c46
--- /dev/null
+++ b/core/res/res/values-mcc240-mnc05/config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2013, 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 my 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Do not set the system language as value of EF LI/EF PL -->
+    <bool name="config_use_sim_language_file">false</bool>
+
+</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a5960a9..a19bc20 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1759,6 +1759,28 @@
     -->
     <bool name="config_enableWifiDisplay">false</bool>
 
+    <!-- The color transform values that correspond to each respective configuration mode for the
+         built-in display, or -1 if the mode is unsupported by the device. The possible
+         configuration modes are:
+            1. Wide-gamut ("Vibrant")
+            2. Adobe RGB ("Natural")
+            3. sRGB ("Standard")
+
+        For example, if a device had Wide-gamut as color transform mode 1, sRGB mode as color
+        transform mode 7, and did not support Adobe RGB at all this would look like:
+
+            <integer-array name="config_colorTransforms">
+                <item>1</item>
+                <item>-1</item>
+                <item>7</item>
+            </integer-array>
+    -->
+    <integer-array name="config_colorTransforms">
+        <item>-1</item>
+        <item>-1</item>
+        <item>-1</item>
+    </integer-array>
+
     <!-- When true use the linux /dev/input/event subsystem to detect the switch changes
          on the headphone/microphone jack. When false use the older uevent framework. -->
     <bool name="config_useDevInputEventForAudioJack">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 16e44b86..4edc8471 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1133,6 +1133,7 @@
   <java-symbol type="array" name="config_telephonyHardware" />
   <java-symbol type="array" name="config_keySystemUuidMapping" />
   <java-symbol type="array" name="config_gpsParameters" />
+  <java-symbol type="array" name="config_colorTransforms" />
 
   <java-symbol type="drawable" name="default_wallpaper" />
   <java-symbol type="drawable" name="indicator_input_error" />
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index 28d99d8..ddd0ca2 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -47,17 +47,38 @@
       <value>0.2</value> <!-- ~2mA -->
       <value>0.1</value> <!-- ~1mA -->
   </array>
-  <!-- Different CPU speeds as reported in
-       /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state -->
-  <array name="cpu.speeds">
+
+  <!-- A list of heterogeneous CPU clusters, where the value for each cluster represents the
+       number of CPU cores for that cluster.
+
+       Ex:
+       <array name="cpu.clusters.cores">
+         <value>4</value> // cluster 0 has cpu0, cpu1, cpu2, cpu3
+         <value>2</value> // cluster 1 has cpu4, cpu5
+       </array> -->
+  <array name="cpu.clusters.cores">
+      <value>1</value> <!-- cluster 0 has cpu0 -->
+  </array>
+
+    <!-- Different CPU speeds for cluster 0 as reported in
+       /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state.
+
+       There must be one of these for each cluster, labeled:
+       cpu.speeds.cluster0, cpu.speeds.cluster1, etc... -->
+  <array name="cpu.speeds.cluster0">
       <value>400000</value> <!-- 400 MHz CPU speed -->
   </array>
-  <!-- Current when CPU is idle -->
-  <item name="cpu.idle">0.1</item>
-  <!-- Current at each CPU speed, as per 'cpu.speeds' -->
-  <array name="cpu.active">
+
+  <!-- Current at each CPU speed for cluster 0, as per 'cpu.speeds.cluster0'.
+       Like cpu.speeds.cluster0, there must be one of these present for
+       each heterogeneous CPU cluster. -->
+  <array name="cpu.active.cluster0">
       <value>0.1</value>  <!-- ~100mA -->
   </array>
+
+  <!-- Current when CPU is idle -->
+  <item name="cpu.idle">0.1</item>
+
   <!-- This is the battery capacity in mAh (measured at nominal voltage) -->
   <item name="battery.capacity">1000</item>
 
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 3eb13ab..cc0943f 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -24,6 +24,7 @@
     utils/GLUtils.cpp \
     utils/LinearAllocator.cpp \
     utils/NinePatchImpl.cpp \
+    utils/StringUtils.cpp \
     AmbientShadow.cpp \
     AnimationContext.cpp \
     Animator.cpp \
@@ -168,7 +169,8 @@
     unit_tests/CanvasStateTests.cpp \
     unit_tests/ClipAreaTests.cpp \
     unit_tests/DamageAccumulatorTests.cpp \
-    unit_tests/LinearAllocatorTests.cpp
+    unit_tests/LinearAllocatorTests.cpp \
+    unit_tests/StringUtilsTests.cpp
 
 include $(BUILD_NATIVE_TEST)
 
diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h
index 5808aac..e98fa04 100644
--- a/libs/hwui/Debug.h
+++ b/libs/hwui/Debug.h
@@ -20,9 +20,6 @@
 // Turn on to check for OpenGL errors on each frame
 #define DEBUG_OPENGL 1
 
-// Turn on to display informations about the GPU
-#define DEBUG_EXTENSIONS 0
-
 // Turn on to enable initialization information
 #define DEBUG_INIT 0
 
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 3d350c9..06c8a21 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -18,23 +18,14 @@
 
 #include "Debug.h"
 #include "Properties.h"
+#include "utils/StringUtils.h"
 
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
 #include <GLES2/gl2ext.h>
 #include <utils/Log.h>
 
 namespace android {
-
-using namespace uirenderer;
-ANDROID_SINGLETON_STATIC_INSTANCE(Extensions);
-
 namespace uirenderer {
 
-///////////////////////////////////////////////////////////////////////////////
-// Defines
-///////////////////////////////////////////////////////////////////////////////
-
 // Debug
 #if DEBUG_EXTENSIONS
     #define EXT_LOGD(...) ALOGD(__VA_ARGS__)
@@ -42,20 +33,16 @@
     #define EXT_LOGD(...)
 #endif
 
-///////////////////////////////////////////////////////////////////////////////
-// Constructors
-///////////////////////////////////////////////////////////////////////////////
 
 Extensions::Extensions() {
-    // Query GL extensions
-    findExtensions((const char*) glGetString(GL_EXTENSIONS), mGlExtensionList);
-    mHasNPot = hasGlExtension("GL_OES_texture_npot");
-    mHasFramebufferFetch = hasGlExtension("GL_NV_shader_framebuffer_fetch");
-    mHasDiscardFramebuffer = hasGlExtension("GL_EXT_discard_framebuffer");
-    mHasDebugMarker = hasGlExtension("GL_EXT_debug_marker");
-    mHas1BitStencil = hasGlExtension("GL_OES_stencil1");
-    mHas4BitStencil = hasGlExtension("GL_OES_stencil4");
-    mHasUnpackSubImage = hasGlExtension("GL_EXT_unpack_subimage");
+    StringCollection extensions((const char*) glGetString(GL_EXTENSIONS));
+    mHasNPot = extensions.has("GL_OES_texture_npot");
+    mHasFramebufferFetch = extensions.has("GL_NV_shader_framebuffer_fetch");
+    mHasDiscardFramebuffer = extensions.has("GL_EXT_discard_framebuffer");
+    mHasDebugMarker = extensions.has("GL_EXT_debug_marker");
+    mHas1BitStencil = extensions.has("GL_OES_stencil1");
+    mHas4BitStencil = extensions.has("GL_OES_stencil4");
+    mHasUnpackSubImage = extensions.has("GL_EXT_unpack_subimage");
 
     const char* version = (const char*) glGetString(GL_VERSION);
 
@@ -78,40 +65,5 @@
     }
 }
 
-///////////////////////////////////////////////////////////////////////////////
-// Methods
-///////////////////////////////////////////////////////////////////////////////
-
-bool Extensions::hasGlExtension(const char* extension) const {
-   const String8 s(extension);
-   return mGlExtensionList.indexOf(s) >= 0;
-}
-
-bool Extensions::hasEglExtension(const char* extension) const {
-   const String8 s(extension);
-   return mEglExtensionList.indexOf(s) >= 0;
-}
-
-void Extensions::findExtensions(const char* extensions, SortedVector<String8>& list) const {
-    const char* current = extensions;
-    const char* head = current;
-    EXT_LOGD("Available extensions:");
-    do {
-        head = strchr(current, ' ');
-        String8 s(current, head ? head - current : strlen(current));
-        if (s.length()) {
-            list.add(s);
-            EXT_LOGD("  %s", s.string());
-        }
-        current = head + 1;
-    } while (head);
-}
-
-void Extensions::dump() const {
-   ALOGD("%s", (const char*) glGetString(GL_VERSION));
-   ALOGD("Supported GL extensions:\n%s", (const char*) glGetString(GL_EXTENSIONS));
-   ALOGD("Supported EGL extensions:\n%s", eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS));
-}
-
 }; // namespace uirenderer
 }; // namespace android
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 636b631..0a30d16 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -50,17 +50,7 @@
     inline int getMajorGlVersion() const { return mVersionMajor; }
     inline int getMinorGlVersion() const { return mVersionMinor; }
 
-    bool hasGlExtension(const char* extension) const;
-    bool hasEglExtension(const char* extension) const;
-
-    void dump() const;
-
 private:
-    void findExtensions(const char* extensions, SortedVector<String8>& list) const;
-
-    SortedVector<String8> mGlExtensionList;
-    SortedVector<String8> mEglExtensionList;
-
     bool mHasNPot;
     bool mHasFramebufferFetch;
     bool mHasDiscardFramebuffer;
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index e27b26b..69559a7 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -435,7 +435,6 @@
 
     mOutGlop->fill.texture = { &texture,
             GL_TEXTURE_2D, GL_LINEAR, GL_CLAMP_TO_EDGE, nullptr };
-    mOutGlop->fill.color = { alpha, alpha, alpha, alpha };
 
     setFill(SK_ColorWHITE, alpha, mode, modeUsage, nullptr, colorFilter);
 
@@ -449,7 +448,6 @@
 
     mOutGlop->fill.texture = { &(layer.getTexture()),
             layer.getRenderTarget(), GL_LINEAR, GL_CLAMP_TO_EDGE, &layer.getTexTransform() };
-    mOutGlop->fill.color = { alpha, alpha, alpha, alpha };
 
     setFill(SK_ColorWHITE, alpha, layer.getMode(), Blend::ModeOrderSwap::NoSwap,
             nullptr, layer.getColorFilter());
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index ed853f7..98e6146 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -77,7 +77,7 @@
         , canvasContext(clone.canvasContext)
     {}
 
-    const TraversalMode mode;
+    TraversalMode mode;
     // TODO: Remove this? Currently this is used to signal to stop preparing
     // textures if we run out of cache space.
     bool prepareTextures;
diff --git a/libs/hwui/renderstate/Blend.cpp b/libs/hwui/renderstate/Blend.cpp
index b21e15e..93f787d 100644
--- a/libs/hwui/renderstate/Blend.cpp
+++ b/libs/hwui/renderstate/Blend.cpp
@@ -98,20 +98,6 @@
     // gl blending off by default
 }
 
-void Blend::enable(SkXfermode::Mode mode, ModeOrderSwap modeUsage) {
-    GLenum srcMode;
-    GLenum dstMode;
-    getFactors(mode, modeUsage, &srcMode, &dstMode);
-    setFactors(srcMode, dstMode);
-}
-
-void Blend::disable() {
-    if (mEnabled) {
-        glDisable(GL_BLEND);
-        mEnabled = false;
-    }
-}
-
 void Blend::invalidate() {
     syncEnabled();
     mSrcMode = mDstMode = GL_ZERO;
@@ -132,8 +118,13 @@
 
 void Blend::setFactors(GLenum srcMode, GLenum dstMode) {
     if (srcMode == GL_ZERO && dstMode == GL_ZERO) {
-        disable();
+        // disable blending
+        if (mEnabled) {
+            glDisable(GL_BLEND);
+            mEnabled = false;
+        }
     } else {
+        // enable blending
         if (!mEnabled) {
             glEnable(GL_BLEND);
             mEnabled = true;
diff --git a/libs/hwui/renderstate/Blend.h b/libs/hwui/renderstate/Blend.h
index dcc681d..df9e5a8 100644
--- a/libs/hwui/renderstate/Blend.h
+++ b/libs/hwui/renderstate/Blend.h
@@ -34,9 +34,6 @@
         NoSwap,
         Swap,
     };
-
-    void enable(SkXfermode::Mode mode, ModeOrderSwap modeUsage);
-    void disable();
     void syncEnabled();
 
     static void getFactors(SkXfermode::Mode mode, ModeOrderSwap modeUsage,
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index b74b508..9dc5b45 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -60,9 +60,10 @@
         , mEglManager(thread.eglManager())
         , mOpaque(!translucent)
         , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
-        , mRootRenderNode(rootRenderNode)
         , mJankTracker(thread.timeLord().frameIntervalNanos())
-        , mProfiler(mFrames) {
+        , mProfiler(mFrames)
+        , mContentOverdrawProtectionBounds(0, 0, 0, 0) {
+    mRenderNodes.emplace_back(rootRenderNode);
     mRenderThread.renderState().registerCanvasContext(this);
     mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
 }
@@ -172,7 +173,8 @@
     return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame);
 }
 
-void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued) {
+void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
+        int64_t syncQueued, RenderNode* target) {
     mRenderThread.removeFrameCallback(this);
 
     // If the previous frame was dropped we don't need to hold onto it, so
@@ -189,7 +191,13 @@
     info.canvasContext = this;
 
     mAnimationContext->startFrame(info.mode);
-    mRootRenderNode->prepareTree(info);
+    for (const sp<RenderNode>& node : mRenderNodes) {
+        // Only the primary target node will be drawn full - all other nodes would get drawn in
+        // real time mode. In case of a window, the primary node is the window content and the other
+        // node(s) are non client / filler nodes.
+        info.mode = (node.get() == target ? TreeInfo::MODE_FULL : TreeInfo::MODE_RT_ONLY);
+        node->prepareTree(info);
+    }
     mAnimationContext->runRemainingAnimations(info);
 
     freePrefetechedLayers();
@@ -299,7 +307,95 @@
             dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom, mOpaque);
 
     Rect outBounds;
-    mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);
+    // It there are multiple render nodes, they are as follows:
+    // #0 - backdrop
+    // #1 - content (with - and clipped to - bounds mContentOverdrawProtectionBounds)
+    // #2 - frame
+    // Usually the backdrop cannot be seen since it will be entirely covered by the content. While
+    // resizing however it might become partially visible. The following render loop will crop the
+    // backdrop against the content and draw the remaining part of it. It will then crop the content
+    // against the backdrop (since that indicates a shrinking of the window) and then the frame
+    // around everything.
+    // The bounds of the backdrop against which the content should be clipped.
+    Rect backdropBounds = mContentOverdrawProtectionBounds;
+    // If there is no content bounds we ignore the layering as stated above and start with 2.
+    int layer = mContentOverdrawProtectionBounds.isEmpty() ? 2 : 0;
+    // Draw all render nodes. Note that
+    for (const sp<RenderNode>& node : mRenderNodes) {
+        if (layer == 0) { // Backdrop.
+            // Draw the backdrop clipped to the inverse content bounds.
+            const RenderProperties& properties = node->properties();
+            Rect targetBounds(properties.getLeft(), properties.getTop(),
+                              properties.getRight(), properties.getBottom());
+            // Remember the intersection of the target bounds and the intersection bounds against
+            // which we have to crop the content.
+            backdropBounds.intersect(targetBounds);
+            // Check if we have to draw something on the left side ...
+            if (targetBounds.left < mContentOverdrawProtectionBounds.left) {
+                mCanvas->save(SkCanvas::kClip_SaveFlag);
+                if (mCanvas->clipRect(targetBounds.left, targetBounds.top,
+                                      mContentOverdrawProtectionBounds.left, targetBounds.bottom,
+                                      SkRegion::kIntersect_Op)) {
+                    mCanvas->drawRenderNode(node.get(), outBounds);
+                }
+                // Reduce the target area by the area we have just painted.
+                targetBounds.left = std::min(mContentOverdrawProtectionBounds.left,
+                                             targetBounds.right);
+                mCanvas->restore();
+            }
+            // ... or on the right side ...
+            if (targetBounds.right > mContentOverdrawProtectionBounds.right &&
+                !targetBounds.isEmpty()) {
+                mCanvas->save(SkCanvas::kClip_SaveFlag);
+                if (mCanvas->clipRect(mContentOverdrawProtectionBounds.right, targetBounds.top,
+                                      targetBounds.right, targetBounds.bottom,
+                                      SkRegion::kIntersect_Op)) {
+                    mCanvas->drawRenderNode(node.get(), outBounds);
+                }
+                // Reduce the target area by the area we have just painted.
+                targetBounds.right = std::max(targetBounds.left,
+                                              mContentOverdrawProtectionBounds.right);
+                mCanvas->restore();
+            }
+            // ... or at the top ...
+            if (targetBounds.top < mContentOverdrawProtectionBounds.top &&
+                !targetBounds.isEmpty()) {
+                mCanvas->save(SkCanvas::kClip_SaveFlag);
+                if (mCanvas->clipRect(targetBounds.left, targetBounds.top, targetBounds.right,
+                                      mContentOverdrawProtectionBounds.top,
+                                      SkRegion::kIntersect_Op)) {
+                    mCanvas->drawRenderNode(node.get(), outBounds);
+                }
+                // Reduce the target area by the area we have just painted.
+                targetBounds.top = std::min(mContentOverdrawProtectionBounds.top,
+                                            targetBounds.bottom);
+                mCanvas->restore();
+            }
+            // ... or at the bottom.
+            if (targetBounds.bottom > mContentOverdrawProtectionBounds.bottom &&
+                !targetBounds.isEmpty()) {
+                mCanvas->save(SkCanvas::kClip_SaveFlag);
+                if (mCanvas->clipRect(targetBounds.left,
+                                      mContentOverdrawProtectionBounds.bottom, targetBounds.right,
+                                      targetBounds.bottom, SkRegion::kIntersect_Op)) {
+                    mCanvas->drawRenderNode(node.get(), outBounds);
+                }
+                mCanvas->restore();
+            }
+        } else if (layer == 1) { // Content
+            // It gets cropped against the bounds of the backdrop to stay inside.
+            mCanvas->save(SkCanvas::kClip_SaveFlag);
+            if (mCanvas->clipRect(backdropBounds.left, backdropBounds.top,
+                                  backdropBounds.right, backdropBounds.bottom,
+                                  SkRegion::kIntersect_Op)) {
+                mCanvas->drawRenderNode(node.get(), outBounds);
+            }
+            mCanvas->restore();
+        } else { // draw the rest on top at will!
+            mCanvas->drawRenderNode(node.get(), outBounds);
+        }
+        layer++;
+    }
 
     profiler().draw(mCanvas);
 
@@ -343,7 +439,10 @@
     if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) {
         return;
     }
+    prepareAndDraw(nullptr);
+}
 
+void CanvasContext::prepareAndDraw(RenderNode* node) {
     ATRACE_CALL();
 
     int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
@@ -353,7 +452,7 @@
                 mRenderThread.timeLord().latestVsync());
 
     TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState());
-    prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC));
+    prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC), node);
     if (info.out.canDrawThisFrame) {
         draw();
     }
@@ -423,7 +522,9 @@
     stopDrawing();
     if (mEglManager.hasEglContext()) {
         freePrefetechedLayers();
-        mRootRenderNode->destroyHardwareResources();
+        for (const sp<RenderNode>& node : mRenderNodes) {
+            node->destroyHardwareResources();
+        }
         Caches& caches = Caches::getInstance();
         // Make sure to release all the textures we were owning as there won't
         // be another draw
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 6a79320..1c3845c 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -34,6 +34,7 @@
 
 #include <set>
 #include <string>
+#include <vector>
 
 namespace android {
 namespace uirenderer {
@@ -77,12 +78,14 @@
     void setOpaque(bool opaque);
     void makeCurrent();
     void processLayerUpdate(DeferredLayerUpdater* layerUpdater);
-    void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued);
+    void prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
+            int64_t syncQueued, RenderNode* target);
     void draw();
     void destroy();
 
     // IFrameCallback, Chroreographer-driven frame callback entry point
     virtual void doFrame() override;
+    void prepareAndDraw(RenderNode* node);
 
     void buildLayer(RenderNode* node);
     bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
@@ -113,6 +116,20 @@
 
     void serializeDisplayListTree();
 
+    void addRenderNode(RenderNode* node, bool placeFront) {
+        int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size());
+        mRenderNodes.emplace( mRenderNodes.begin() + pos, node);
+    }
+
+    void removeRenderNode(RenderNode* node) {
+        mRenderNodes.erase(std::remove(mRenderNodes.begin(), mRenderNodes.end(), node),
+                mRenderNodes.end());
+    }
+
+    void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) {
+        mContentOverdrawProtectionBounds.set(left, top, right, bottom);
+    }
+
 private:
     friend class RegisterFrameCallbackTask;
     // TODO: Replace with something better for layer & other GL object
@@ -138,7 +155,7 @@
     DamageAccumulator mDamageAccumulator;
     std::unique_ptr<AnimationContext> mAnimationContext;
 
-    const sp<RenderNode> mRootRenderNode;
+    std::vector< sp<RenderNode> > mRenderNodes;
 
     FrameInfo* mCurrentFrameInfo = nullptr;
     // Ring buffer large enough for 2 seconds worth of frames
@@ -148,6 +165,9 @@
     FrameInfoVisualizer mProfiler;
 
     std::set<RenderNode*> mPrefetechedLayers;
+
+    // Stores the bounds of the main content.
+    Rect mContentOverdrawProtectionBounds;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 198906c..a47c9ec 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -38,9 +38,11 @@
 DrawFrameTask::~DrawFrameTask() {
 }
 
-void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context) {
+void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context,
+        RenderNode* targetNode) {
     mRenderThread = thread;
     mContext = context;
+    mTargetNode = targetNode;
 }
 
 void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -118,7 +120,7 @@
         mContext->processLayerUpdate(mLayers[i].get());
     }
     mLayers.clear();
-    mContext->prepareTree(info, mFrameInfo, mSyncQueued);
+    mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
 
     // This is after the prepareTree so that any pending operations
     // (RenderNode tree state, prefetched layers, etc...) will be flushed.
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index ebefcba..68ee897 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -57,7 +57,7 @@
     DrawFrameTask();
     virtual ~DrawFrameTask();
 
-    void setContext(RenderThread* thread, CanvasContext* context);
+    void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode);
 
     void pushLayerUpdate(DeferredLayerUpdater* layer);
     void removeLayerUpdate(DeferredLayerUpdater* layer);
@@ -78,6 +78,7 @@
 
     RenderThread* mRenderThread;
     CanvasContext* mContext;
+    RenderNode* mTargetNode = nullptr;
 
     /*********************************************
      *  Single frame data
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index d2ce49f..c9b9637 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -20,6 +20,7 @@
 #include "Properties.h"
 #include "RenderThread.h"
 #include "renderstate/RenderState.h"
+#include "utils/StringUtils.h"
 
 #include <cutils/log.h>
 #include <cutils/properties.h>
@@ -133,12 +134,9 @@
 }
 
 void EglManager::initExtensions() {
-    std::string extensions(eglQueryString(mEglDisplay, EGL_EXTENSIONS));
-    auto has = [&](const char* ext) {
-        return extensions.find(ext) != std::string::npos;
-    };
-    EglExtensions.bufferAge = has("EGL_EXT_buffer_age");
-    EglExtensions.setDamage = has("EGL_KHR_partial_update");
+    StringCollection extensions(eglQueryString(mEglDisplay, EGL_EXTENSIONS));
+    EglExtensions.bufferAge = extensions.has("EGL_EXT_buffer_age");
+    EglExtensions.setDamage = extensions.has("EGL_KHR_partial_update");
 }
 
 bool EglManager::hasEglContext() {
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index b838811..f43a769 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -74,7 +74,7 @@
     args->thread = &mRenderThread;
     args->contextFactory = contextFactory;
     mContext = (CanvasContext*) postAndWait(task);
-    mDrawFrameTask.setContext(&mRenderThread, mContext);
+    mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
 }
 
 RenderProxy::~RenderProxy() {
@@ -91,7 +91,7 @@
         SETUP_TASK(destroyContext);
         args->context = mContext;
         mContext = nullptr;
-        mDrawFrameTask.setContext(nullptr, nullptr);
+        mDrawFrameTask.setContext(nullptr, nullptr, nullptr);
         // This is also a fence as we need to be certain that there are no
         // outstanding mDrawFrame tasks posted before it is destroyed
         postAndWait(task);
@@ -461,7 +461,8 @@
     staticPostAndWait(task);
 }
 
-CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map, size_t size) {
+CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map,
+               size_t size) {
     CanvasContext::setTextureAtlas(*args->thread, args->buffer, args->map, args->size);
     args->buffer->decStrong(nullptr);
     return nullptr;
@@ -490,6 +491,61 @@
     post(task);
 }
 
+CREATE_BRIDGE3(addRenderNode, CanvasContext* context, RenderNode* node, bool placeFront) {
+    args->context->addRenderNode(args->node, args->placeFront);
+    return nullptr;
+}
+
+void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) {
+    SETUP_TASK(addRenderNode);
+    args->context = mContext;
+    args->node = node;
+    args->placeFront = placeFront;
+    post(task);
+}
+
+CREATE_BRIDGE2(removeRenderNode, CanvasContext* context, RenderNode* node) {
+    args->context->removeRenderNode(args->node);
+    return nullptr;
+}
+
+void RenderProxy::removeRenderNode(RenderNode* node) {
+    SETUP_TASK(removeRenderNode);
+    args->context = mContext;
+    args->node = node;
+    post(task);
+}
+
+CREATE_BRIDGE2(drawRenderNode, CanvasContext* context, RenderNode* node) {
+    args->context->prepareAndDraw(args->node);
+    return nullptr;
+}
+
+void RenderProxy::drawRenderNode(RenderNode* node) {
+    SETUP_TASK(drawRenderNode);
+    args->context = mContext;
+    args->node = node;
+    // Be pseudo-thread-safe and don't use any member variables
+    staticPostAndWait(task);
+}
+
+CREATE_BRIDGE5(setContentOverdrawProtectionBounds, CanvasContext* context, int left, int top,
+        int right, int bottom) {
+    args->context->setContentOverdrawProtectionBounds(args->left, args->top, args->right,
+                                                      args->bottom);
+    return nullptr;
+}
+
+void RenderProxy::setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) {
+    SETUP_TASK(setContentOverdrawProtectionBounds);
+    args->context = mContext;
+    args->left = left;
+    args->top = top;
+    args->right = right;
+    args->bottom = bottom;
+    staticPostAndWait(task);
+}
+
 CREATE_BRIDGE1(serializeDisplayListTree, CanvasContext* context) {
     args->context->serializeDisplayListTree();
     return nullptr;
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index e7356db..046f24a 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -106,6 +106,11 @@
 
     ANDROID_API void serializeDisplayListTree();
 
+    ANDROID_API void addRenderNode(RenderNode* node, bool placeFront);
+    ANDROID_API void removeRenderNode(RenderNode* node);
+    ANDROID_API void drawRenderNode(RenderNode* node);
+    ANDROID_API void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom);
+
 private:
     RenderThread& mRenderThread;
     CanvasContext* mContext;
diff --git a/libs/hwui/unit_tests/StringUtilsTests.cpp b/libs/hwui/unit_tests/StringUtilsTests.cpp
new file mode 100644
index 0000000..5174ae9
--- /dev/null
+++ b/libs/hwui/unit_tests/StringUtilsTests.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "utils/StringUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+TEST(StringUtils, simpleBuildSet) {
+    StringCollection collection("a b c");
+
+    EXPECT_TRUE(collection.has("a"));
+    EXPECT_TRUE(collection.has("b"));
+    EXPECT_TRUE(collection.has("c"));
+    EXPECT_FALSE(collection.has("d"));
+}
+
+TEST(StringUtils, advancedBuildSet) {
+    StringCollection collection("GL_ext1 GL_ext2 GL_ext3");
+
+    EXPECT_TRUE(collection.has("GL_ext1"));
+    EXPECT_FALSE(collection.has("GL_ext")); // string present, but not in list
+}
+
+};
+};
diff --git a/libs/hwui/utils/StringUtils.cpp b/libs/hwui/utils/StringUtils.cpp
new file mode 100644
index 0000000..a1df0e7
--- /dev/null
+++ b/libs/hwui/utils/StringUtils.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include "StringUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+StringCollection::StringCollection(const char* spacedList) {
+    const char* current = spacedList;
+    const char* head = current;
+    do {
+        head = strchr(current, ' ');
+        std::string s(current, head ? head - current : strlen(current));
+        if (s.length()) {
+            mSet.insert(s);
+        }
+        current = head + 1;
+    } while (head);
+}
+
+bool StringCollection::has(const char* s) {
+    return mSet.find(std::string(s)) != mSet.end();
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/utils/StringUtils.h b/libs/hwui/utils/StringUtils.h
new file mode 100644
index 0000000..ef2a6d5
--- /dev/null
+++ b/libs/hwui/utils/StringUtils.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+#ifndef STRING_UTILS_H
+#define STRING_UTILS_H
+
+#include <string>
+#include <unordered_set>
+
+namespace android {
+namespace uirenderer {
+
+class StringCollection {
+public:
+    StringCollection(const char* spacedList);
+    bool has(const char* string);
+private:
+    std::unordered_set<std::string> mSet;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* GLUTILS_H */
diff --git a/packages/DocumentsUI/res/layout/directory_cluster.xml b/packages/DocumentsUI/res/layout/directory_cluster.xml
index e47e196..8245e53 100644
--- a/packages/DocumentsUI/res/layout/directory_cluster.xml
+++ b/packages/DocumentsUI/res/layout/directory_cluster.xml
@@ -18,6 +18,13 @@
     android:layout_height="match_parent"
     android:orientation="vertical">
 
+    <FrameLayout
+        android:id="@+id/container_message_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:elevation="8dp"
+        android:background="@color/material_grey_50"/>
+
     <com.android.documentsui.DirectoryContainerView
         android:id="@+id/container_directory"
         android:layout_width="match_parent"
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index 7aff497..f5c73d5 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -34,18 +34,42 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
-    <android.support.v7.widget.RecyclerView
-        android:id="@+id/recyclerView"
-        android:scrollbars="vertical"
+    <LinearLayout
+        android:id="@+id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:paddingStart="@dimen/grid_padding_horiz"
-        android:paddingEnd="@dimen/grid_padding_horiz"
-        android:paddingTop="@dimen/grid_padding_vert"
-        android:paddingBottom="@dimen/grid_padding_vert"
-        android:clipToPadding="false"
-        android:scrollbarStyle="outsideOverlay"
-        android:drawSelectorOnTop="true"
-        android:background="@color/directory_background" />
+        android:orientation="vertical"
+        android:animateLayoutChanges="true">
+
+        <FrameLayout
+            android:id="@+id/container_message_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:elevation="8dp"
+            android:background="@color/material_grey_50"
+            android:visibility="gone"/>
+      
+        <!-- This FrameLayout works around b/24189541 -->
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+          
+            <android.support.v7.widget.RecyclerView
+                android:id="@+id/recyclerView"
+                android:scrollbars="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:paddingStart="@dimen/grid_padding_horiz"
+                android:paddingEnd="@dimen/grid_padding_horiz"
+                android:paddingTop="@dimen/grid_padding_vert"
+                android:paddingBottom="@dimen/grid_padding_vert"
+                android:clipToPadding="false"
+                android:scrollbarStyle="outsideOverlay"
+                android:drawSelectorOnTop="true"
+                android:background="@color/directory_background" />
+            
+        </FrameLayout>
+        
+    </LinearLayout>
 
 </com.android.documentsui.DirectoryView>
diff --git a/packages/DocumentsUI/res/layout/fragment_message_bar.xml b/packages/DocumentsUI/res/layout/fragment_message_bar.xml
new file mode 100644
index 0000000..47e4e77
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/fragment_message_bar.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:animateLayoutChanges="true"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:orientation="vertical"
+    android:paddingLeft="@dimen/list_item_padding"
+    android:paddingRight="@dimen/list_item_padding"
+    android:paddingTop="@dimen/list_item_padding">
+
+    <LinearLayout
+        android:id="@+id/container_info"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:orientation="horizontal"
+        android:visibility="gone">
+
+        <FrameLayout
+            android:layout_height="@dimen/icon_size"
+            android:layout_width="@dimen/icon_size">
+
+            <ImageView
+                android:contentDescription="@null"
+                android:id="@+id/icon_info"
+                android:layout_height="match_parent"
+                android:layout_width="wrap_content"
+                android:scaleType="centerInside"/>
+
+        </FrameLayout>
+
+        <TextView
+            android:id="@+id/textview_info"
+            android:layout_gravity="center_vertical"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:selectAllOnFocus="true"/>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/container_error"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:orientation="horizontal"
+        android:visibility="gone">
+
+        <FrameLayout
+            android:layout_height="@dimen/icon_size"
+            android:layout_width="@dimen/icon_size">
+
+            <ImageView
+                android:contentDescription="@null"
+                android:id="@+id/icon_error"
+                android:layout_height="match_parent"
+                android:layout_width="wrap_content"
+                android:scaleType="centerInside"/>
+
+        </FrameLayout>
+
+        <TextView
+            android:id="@+id/textview_error"
+            android:layout_gravity="center_vertical"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:selectAllOnFocus="true"/>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_gravity="right"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/button_dismiss"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:text="@string/button_dismiss"
+            style="?android:attr/buttonBarPositiveButtonStyle"/>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/DocumentsUI/res/layout/item_loading_grid.xml b/packages/DocumentsUI/res/layout/item_loading_grid.xml
deleted file mode 100644
index 147dfd4..0000000
--- a/packages/DocumentsUI/res/layout/item_loading_grid.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-
-<com.android.documentsui.GridItem xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/grid_height"
-    android:orientation="horizontal">
-
-    <ProgressBar
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:indeterminate="true"
-        style="?android:attr/progressBarStyle" />
-
-</com.android.documentsui.GridItem>
diff --git a/packages/DocumentsUI/res/layout/item_loading_list.xml b/packages/DocumentsUI/res/layout/item_loading_list.xml
deleted file mode 100644
index 6f214ed..0000000
--- a/packages/DocumentsUI/res/layout/item_loading_list.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="@dimen/list_item_height">
-
-    <ProgressBar
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:indeterminate="true"
-        style="?android:attr/progressBarStyle" />
-
-</FrameLayout>
diff --git a/packages/DocumentsUI/res/layout/item_message_grid.xml b/packages/DocumentsUI/res/layout/item_message_grid.xml
deleted file mode 100644
index 45d61a5..0000000
--- a/packages/DocumentsUI/res/layout/item_message_grid.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-
-<com.android.documentsui.GridItem xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/grid_height"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingTop="8dp"
-    android:paddingBottom="8dp">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:gravity="center">
-
-        <ImageView
-            android:id="@android:id/icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:contentDescription="@null" />
-
-        <TextView
-            android:id="@android:id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:maxLines="4"
-            android:ellipsize="end"
-            android:paddingTop="8dp"
-            android:textAlignment="viewStart"
-            android:textAppearance="@android:style/TextAppearance.Material.Body1"
-            android:textColor="?android:attr/textColorPrimary" />
-
-    </LinearLayout>
-
-</com.android.documentsui.GridItem>
diff --git a/packages/DocumentsUI/res/layout/item_message_list.xml b/packages/DocumentsUI/res/layout/item_message_list.xml
deleted file mode 100644
index 44c8baf..0000000
--- a/packages/DocumentsUI/res/layout/item_message_list.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="@dimen/list_item_height"
-    android:paddingStart="@dimen/list_item_padding"
-    android:paddingEnd="@dimen/list_item_padding"
-    android:paddingTop="8dp"
-    android:paddingBottom="8dp"
-    android:gravity="center_vertical"
-    android:orientation="horizontal"
-    android:baselineAligned="false">
-
-    <FrameLayout
-        android:layout_width="@dimen/icon_size"
-        android:layout_height="@dimen/icon_size"
-        android:layout_marginEnd="16dp">
-
-        <ImageView
-            android:id="@android:id/icon"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:scaleType="centerInside"
-            android:contentDescription="@null" />
-
-    </FrameLayout>
-
-    <TextView
-        android:id="@android:id/title"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:maxLines="2"
-        android:ellipsize="end"
-        android:textAlignment="viewStart"
-        android:textAppearance="@android:style/TextAppearance.Material.Body1"
-        android:textColor="?android:attr/textColorPrimary" />
-
-</LinearLayout>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 4b44d7c..28018f8e 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -79,6 +79,8 @@
     <string name="button_copy">Copy</string>
     <!-- Button label that moves files to the current directory [CHAR LIMIT=24] -->
     <string name="button_move">Move</string>
+    <!-- Button label that hides the error bar [CHAR LIMIT=24] -->
+    <string name="button_dismiss">Dismiss</string>
 
     <!-- Mode that sorts documents by their display name alphabetically [CHAR LIMIT=24] -->
     <string name="sort_name">By name</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 3202876..c2821e1 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -159,6 +159,8 @@
     private GridLayoutManager mGridLayout;
     private int mColumnCount = 1;  // This will get updated when layout changes.
 
+    private MessageBar mMessageBar;
+
     public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
         show(fm, TYPE_NORMAL, root, doc, null, anim);
     }
@@ -220,6 +222,8 @@
         final Resources res = context.getResources();
         final View view = inflater.inflate(R.layout.fragment_directory, container, false);
 
+        mMessageBar = MessageBar.create(getChildFragmentManager());
+
         mEmptyView = view.findViewById(android.R.id.empty);
 
         mRecView = (RecyclerView) view.findViewById(R.id.recyclerView);
@@ -611,7 +615,7 @@
 
         @Override
         public boolean onBeforeItemStateChange(int position, boolean selected) {
-            // Directories and footer items cannot be checked
+            // Directories cannot be checked
             if (selected) {
                 final Cursor cursor = mModel.getItem(position);
                 checkNotNull(cursor, "Cursor cannot be null.");
@@ -880,79 +884,6 @@
         return ((BaseActivity) fragment.getActivity()).getDisplayState();
     }
 
-    private static abstract class Footer {
-        private final int mItemViewType;
-
-        public Footer(int itemViewType) {
-            mItemViewType = itemViewType;
-        }
-
-        public abstract View getView(View convertView, ViewGroup parent);
-
-        public int getItemViewType() {
-            return mItemViewType;
-        }
-    }
-
-    private class LoadingFooter extends Footer {
-        public LoadingFooter() {
-            super(1);
-        }
-
-        @Override
-        public View getView(View convertView, ViewGroup parent) {
-            final Context context = parent.getContext();
-            final State state = getDisplayState(DirectoryFragment.this);
-
-            if (convertView == null) {
-                final LayoutInflater inflater = LayoutInflater.from(context);
-                if (state.derivedMode == MODE_LIST) {
-                    convertView = inflater.inflate(R.layout.item_loading_list, parent, false);
-                } else if (state.derivedMode == MODE_GRID) {
-                    convertView = inflater.inflate(R.layout.item_loading_grid, parent, false);
-                } else {
-                    throw new IllegalStateException();
-                }
-            }
-
-            return convertView;
-        }
-    }
-
-    private class MessageFooter extends Footer {
-        private final int mIcon;
-        private final String mMessage;
-
-        public MessageFooter(int itemViewType, int icon, String message) {
-            super(itemViewType);
-            mIcon = icon;
-            mMessage = message;
-        }
-
-        @Override
-        public View getView(View convertView, ViewGroup parent) {
-            final Context context = parent.getContext();
-            final State state = getDisplayState(DirectoryFragment.this);
-
-            if (convertView == null) {
-                final LayoutInflater inflater = LayoutInflater.from(context);
-                if (state.derivedMode == MODE_LIST) {
-                    convertView = inflater.inflate(R.layout.item_message_list, parent, false);
-                } else if (state.derivedMode == MODE_GRID) {
-                    convertView = inflater.inflate(R.layout.item_message_grid, parent, false);
-                } else {
-                    throw new IllegalStateException();
-                }
-            }
-
-            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
-            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
-            icon.setImageResource(mIcon);
-            title.setText(mMessage);
-            return convertView;
-        }
-    }
-
     // Provide a reference to the views for each data item
     // Complex data items may need more than one view per item, and
     // you provide access to all the views for a data item in a view holder
@@ -971,24 +902,18 @@
 
         private final Context mContext;
         private final LayoutInflater mInflater;
-        // TODO: Bring back support for footers.
-        private final List<Footer> mFooters = new ArrayList<>();
 
         public DocumentsAdapter(Context context) {
             mContext = context;
             mInflater = LayoutInflater.from(context);
         }
 
+        @Override
         public void onModelUpdate(Model model) {
-            mFooters.clear();
-            if (model.info != null) {
-                mFooters.add(new MessageFooter(2, R.drawable.ic_dialog_info, model.info));
-            }
-            if (model.error != null) {
-                mFooters.add(new MessageFooter(3, R.drawable.ic_dialog_alert, model.error));
-            }
-            if (model.isLoading()) {
-                mFooters.add(new LoadingFooter());
+            if (model.info != null || model.error != null) {
+                mMessageBar.setInfo(model.info);
+                mMessageBar.setError(model.error);
+                mMessageBar.show();
             }
 
             if (model.isEmpty()) {
@@ -1000,9 +925,10 @@
             notifyDataSetChanged();
         }
 
+        @Override
         public void onModelUpdateFailed(Exception e) {
+            // TODO: deal with catastrophic update failures
             String error = getString(R.string.query_error);
-            mFooters.add(new MessageFooter(3, R.drawable.ic_dialog_alert, error));
             notifyDataSetChanged();
         }
 
@@ -1222,19 +1148,8 @@
         @Override
         public int getItemCount() {
             return mModel.getItemCount();
-            // return mCursorCount + mFooters.size();
         }
 
-        @Override
-        public int getItemViewType(int position) {
-            final int itemCount = mModel.getItemCount();
-            if (position < itemCount) {
-                return 0;
-            } else {
-                position -= itemCount;
-                return mFooters.get(position).getItemViewType();
-            }
-        }
     }
 
     private static String formatTime(Context context, long when) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MessageBar.java b/packages/DocumentsUI/src/com/android/documentsui/MessageBar.java
new file mode 100644
index 0000000..a48fd5c
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/MessageBar.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2015 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.documentsui;
+
+import android.annotation.Nullable;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * A message bar displaying some info/error messages and a Dismiss button.
+ */
+public class MessageBar extends Fragment {
+    private View mView;
+    private ViewGroup mContainer;
+
+    /**
+     * Creates an instance of a MessageBar. Note that the new MessagBar is not visible by default,
+     * and has to be shown by calling MessageBar.show.
+     */
+    public static MessageBar create(FragmentManager fm) {
+        final MessageBar fragment = new MessageBar();
+
+        final FragmentTransaction ft = fm.beginTransaction();
+        ft.replace(R.id.container_message_bar, fragment);
+        ft.commitAllowingStateLoss();
+
+        return fragment;
+    }
+
+    /**
+     * Sets the info message. Can be null, in which case no info message will be displayed. The
+     * message bar layout will be adjusted accordingly.
+     */
+    public void setInfo(@Nullable String info) {
+        View infoContainer = mView.findViewById(R.id.container_info);
+        if (info != null) {
+            TextView infoText = (TextView) mView.findViewById(R.id.textview_info);
+            infoText.setText(info);
+            infoContainer.setVisibility(View.VISIBLE);
+        } else {
+            infoContainer.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Sets the error message. Can be null, in which case no error message will be displayed. The
+     * message bar layout will be adjusted accordingly.
+     */
+    public void setError(@Nullable String error) {
+        View errorView = mView.findViewById(R.id.container_message_bar);
+        if (error != null) {
+            TextView errorText = (TextView) mView.findViewById(R.id.textview_error);
+            errorText.setText(error);
+            errorView.setVisibility(View.VISIBLE);
+        } else {
+            errorView.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+
+        mView = inflater.inflate(R.layout.fragment_message_bar, container, false);
+
+        ImageView infoIcon = (ImageView) mView.findViewById(R.id.icon_info);
+        infoIcon.setImageResource(R.drawable.ic_dialog_info);
+
+        ImageView errorIcon = (ImageView) mView.findViewById(R.id.icon_error);
+        errorIcon.setImageResource(R.drawable.ic_dialog_alert);
+
+        Button dismiss = (Button) mView.findViewById(R.id.button_dismiss);
+        dismiss.setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        hide();
+                    }
+                });
+
+        mContainer = container;
+
+        return mView;
+    }
+
+    void hide() {
+        // The container view is used to show/hide the error bar. If a container is not provided,
+        // fall back to showing/hiding the error bar View, which also works, but does not provide
+        // the same animated transition.
+        if (mContainer != null) {
+            mContainer.setVisibility(View.GONE);
+        } else {
+            mView.setVisibility(View.GONE);
+        }
+    }
+
+    void show() {
+        // The container view is used to show/hide the error bar. If a container is not provided,
+        // fall back to showing/hiding the error bar View, which also works, but does not provide
+        // the same animated transition.
+        if (mContainer != null) {
+            mContainer.setVisibility(View.VISIBLE);
+        } else {
+            mView.setVisibility(View.VISIBLE);
+        }
+    }
+}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e4fc075..93dc889 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -205,6 +205,7 @@
                   android:stateNotNeeded="true"
                   android:resumeWhilePausing="true"
                   android:screenOrientation="behind"
+                  android:resizeableActivity="true"
                   android:theme="@style/config_recents_activity_theme">
             <intent-filter>
                 <action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index a4acf83..e1a8815 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -45,8 +45,6 @@
             public static final boolean EnableSearchLayout = true;
             // Enables the thumbnail alpha on the front-most task
             public static final boolean EnableThumbnailAlphaOnFrontmost = false;
-            // Enables all system stacks to show up in the same recents stack
-            public static final boolean EnableMultiStackToSingleStack = true;
             // This disables the bitmap and icon caches
             public static final boolean DisableBackgroundCache = false;
             // Enables the simulated task affiliations
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 0cfa7a1..308e7cc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -116,7 +116,7 @@
         /** Preloads the next task */
         public void run() {
             // Temporarily skip this if multi stack is enabled
-            if (mConfig.multiStackEnabled) return;
+            if (mConfig.multiWindowEnabled) return;
 
             RecentsConfiguration config = RecentsConfiguration.getInstance();
             if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
@@ -176,7 +176,6 @@
 
     // Task launching
     RecentsConfiguration mConfig;
-    Rect mWindowRect = new Rect();
     Rect mTaskStackBounds = new Rect();
     Rect mSystemInsets = new Rect();
     TaskViewTransform mTmpTransform = new TaskViewTransform();
@@ -372,9 +371,9 @@
         if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome)) {
             sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
             loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
-            TaskStack top = sInstanceLoadPlan.getAllTaskStacks().get(0);
-            if (top.getTaskCount() > 0) {
-                preCacheThumbnailTransitionBitmapAsync(topTask, top, mDummyStackView,
+            TaskStack stack = sInstanceLoadPlan.getTaskStack();
+            if (stack.getTaskCount() > 0) {
+                preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView,
                         topTaskHome.value);
             }
         }
@@ -388,16 +387,10 @@
     void showRelativeAffiliatedTask(boolean showNextTask) {
         // Return early if there is no focused stack
         int focusedStackId = mSystemServicesProxy.getFocusedStack();
-        TaskStack focusedStack = null;
         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
         loader.preloadTasks(plan, true /* isTopTaskHome */);
-        if (mConfig.multiStackEnabled) {
-            if (focusedStackId < 0) return;
-            focusedStack = plan.getTaskStack(focusedStackId);
-        } else {
-            focusedStack = plan.getAllTaskStacks().get(0);
-        }
+        TaskStack focusedStack = plan.getTaskStack();
 
         // Return early if there are no tasks in the focused stack
         if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
@@ -502,7 +495,8 @@
     /** Prepares the header bar layout. */
     void reloadHeaderBarLayout() {
         Resources res = mContext.getResources();
-        mWindowRect = mSystemServicesProxy.getWindowRect();
+        Rect windowRect = mSystemServicesProxy.getWindowRect();
+
         mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
         mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
         mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
@@ -513,10 +507,10 @@
         // have the right thumbnail bounds to animate to.
         // Note: We have to reload the widget id before we get the task stack bounds below
         if (mSystemServicesProxy.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
-            mConfig.getSearchBarBounds(mWindowRect.width(), mWindowRect.height(),
+            mConfig.getSearchBarBounds(windowRect,
                     mStatusBarHeight, searchBarBounds);
         }
-        mConfig.getAvailableTaskStackBounds(mWindowRect.width(), mWindowRect.height(),
+        mConfig.getAvailableTaskStackBounds(windowRect,
                 mStatusBarHeight, (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), searchBarBounds,
                 mTaskStackBounds);
         if (mConfig.isLandscape && mConfig.hasTransposedNavBar) {
@@ -531,7 +525,7 @@
         TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
         Rect taskStackBounds = new Rect(mTaskStackBounds);
         taskStackBounds.bottom -= mSystemInsets.bottom;
-        algo.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds);
+        algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds);
         Rect taskViewSize = algo.getUntransformedTaskViewSize();
         int taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
         synchronized (mHeaderBarLock) {
@@ -540,6 +534,7 @@
             mHeaderBar.measure(
                     View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY),
                     View.MeasureSpec.makeMeasureSpec(taskBarHeight, View.MeasureSpec.EXACTLY));
+            // TODO: may not be needed
             mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight);
         }
     }
@@ -749,10 +744,9 @@
 
         // Temporarily skip the transition (use a dummy fade) if multi stack is enabled.
         // For multi-stack we need to figure out where each of the tasks are going.
-        if (mConfig.multiStackEnabled) {
+        if (mConfig.multiWindowEnabled) {
             loader.preloadTasks(sInstanceLoadPlan, true);
-            ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks();
-            TaskStack stack = stacks.get(0);
+            TaskStack stack = sInstanceLoadPlan.getTaskStack();
             mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, true);
             TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
                     mDummyStackView.computeStackVisibilityReport();
@@ -765,8 +759,7 @@
         if (!sInstanceLoadPlan.hasTasks()) {
             loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
         }
-        ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks();
-        TaskStack stack = stacks.get(0);
+        TaskStack stack = sInstanceLoadPlan.getTaskStack();
 
         // Prepare the dummy stack for the transition
         mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index d0876fa..4bb3e4a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -205,10 +206,10 @@
         loadOpts.numVisibleTaskThumbnails = mConfig.launchedNumVisibleThumbnails;
         loader.loadTasks(this, plan, loadOpts);
 
-        ArrayList<TaskStack> stacks = plan.getAllTaskStacks();
+        TaskStack stack = plan.getTaskStack();
         mConfig.launchedWithNoRecentTasks = !plan.hasTasks();
         if (!mConfig.launchedWithNoRecentTasks) {
-            mRecentsView.setTaskStacks(stacks);
+            mRecentsView.setTaskStack(stack);
         }
 
         // Create the home intent runnable
@@ -224,20 +225,16 @@
                         R.anim.recents_to_launcher_exit));
 
         // Mark the task that is the launch target
-        int taskStackCount = stacks.size();
         int launchTaskIndexInStack = 0;
         if (mConfig.launchedToTaskId != -1) {
-            for (int i = 0; i < taskStackCount; i++) {
-                TaskStack stack = stacks.get(i);
-                ArrayList<Task> tasks = stack.getTasks();
-                int taskCount = tasks.size();
-                for (int j = 0; j < taskCount; j++) {
-                    Task t = tasks.get(j);
-                    if (t.key.id == mConfig.launchedToTaskId) {
-                        t.isLaunchTarget = true;
-                        launchTaskIndexInStack = tasks.size() - j - 1;
-                        break;
-                    }
+            ArrayList<Task> tasks = stack.getTasks();
+            int taskCount = tasks.size();
+            for (int j = 0; j < taskCount; j++) {
+                Task t = tasks.get(j);
+                if (t.key.id == mConfig.launchedToTaskId) {
+                    t.isLaunchTarget = true;
+                    launchTaskIndexInStack = tasks.size() - j - 1;
+                    break;
                 }
             }
         }
@@ -278,11 +275,7 @@
             MetricsLogger.count(this, "overview_source_home", 1);
         }
         // Keep track of the total stack task count
-        int taskCount = 0;
-        for (int i = 0; i < stacks.size(); i++) {
-            TaskStack stack = stacks.get(i);
-            taskCount += stack.getTaskCount();
-        }
+        int taskCount = stack.getTaskCount();
         MetricsLogger.histogram(this, "overview_task_count", taskCount);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index dfe7e96..b41b5e7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -134,7 +134,7 @@
     public boolean fakeShadows;
 
     /** Dev options and global settings */
-    public boolean multiStackEnabled;
+    public boolean multiWindowEnabled;
     public boolean lockToAppEnabled;
     public boolean developerOptionsEnabled;
     public boolean debugModeEnabled;
@@ -283,7 +283,7 @@
                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED) != 0;
         lockToAppEnabled = ssp.getSystemSetting(context,
                 Settings.System.LOCK_TO_APP_ENABLED) != 0;
-        multiStackEnabled = "true".equals(ssp.getSystemProperty("persist.sys.debug.multi_window"));
+        multiWindowEnabled = "true".equals(ssp.getSystemProperty("persist.sys.debug.multi_window"));
     }
 
     /** Called when the configuration has changed, and we want to reset any configuration specific
@@ -320,14 +320,16 @@
      * Returns the task stack bounds in the current orientation. These bounds do not account for
      * the system insets.
      */
-    public void getAvailableTaskStackBounds(int windowWidth, int windowHeight, int topInset,
+    public void getAvailableTaskStackBounds(Rect windowBounds, int topInset,
             int rightInset, Rect searchBarBounds, Rect taskStackBounds) {
         if (isLandscape && hasTransposedSearchBar) {
             // In landscape, the search bar appears on the left, but we overlay it on top
-            taskStackBounds.set(0, topInset, windowWidth - rightInset, windowHeight);
+            taskStackBounds.set(windowBounds.left, windowBounds.top + topInset,
+                    windowBounds.right - rightInset, windowBounds.bottom);
         } else {
             // In portrait, the search bar appears on the top (which already has the inset)
-            taskStackBounds.set(0, searchBarBounds.bottom, windowWidth, windowHeight);
+            taskStackBounds.set(windowBounds.left, searchBarBounds.bottom,
+                    windowBounds.right, windowBounds.bottom);
         }
     }
 
@@ -335,16 +337,17 @@
      * Returns the search bar bounds in the current orientation.  These bounds do not account for
      * the system insets.
      */
-    public void getSearchBarBounds(int windowWidth, int windowHeight, int topInset,
-            Rect searchBarSpaceBounds) {
+    public void getSearchBarBounds(Rect windowBounds, int topInset, Rect searchBarSpaceBounds) {
         // Return empty rects if search is not enabled
         int searchBarSize = searchBarSpaceHeightPx;
         if (isLandscape && hasTransposedSearchBar) {
             // In landscape, the search bar appears on the left
-            searchBarSpaceBounds.set(0, topInset, searchBarSize, windowHeight);
+            searchBarSpaceBounds.set(windowBounds.left, windowBounds.top + topInset,
+                    windowBounds.left + searchBarSize, windowBounds.bottom);
         } else {
             // In portrait, the search bar appears on the top
-            searchBarSpaceBounds.set(0, topInset, windowWidth, topInset + searchBarSize);
+            searchBarSpaceBounds.set(windowBounds.left, windowBounds.top + topInset,
+                    windowBounds.right, windowBounds.top + topInset + searchBarSize);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
index 300ea2a..703c7d2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
@@ -24,7 +24,6 @@
 import android.content.DialogInterface;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.Button;
@@ -32,11 +31,8 @@
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.views.RecentsView;
 
-import java.util.ArrayList;
-
 /**
  * A helper for the dialogs that show when task debugging is on.
  */
@@ -216,7 +212,7 @@
 
         // In debug mode, we force all task to be resizeable regardless of the
         // current app configuration.
-        if (RecentsConfiguration.getInstance().multiStackEnabled) {
+        if (RecentsConfiguration.getInstance().multiWindowEnabled) {
             for (int i = additionalTasks; i >= 0; --i) {
                 if (mTasks[i] != null) {
                     mSsp.setTaskResizeable(mTasks[i].key.id);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index bead1b0..5790ca6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -641,12 +641,19 @@
      */
     public Rect getWindowRect() {
         Rect windowRect = new Rect();
-        if (mWm == null) return windowRect;
+        if (mIam == null) return windowRect;
 
-        Point p = new Point();
-        mWm.getDefaultDisplay().getRealSize(p);
-        windowRect.set(0, 0, p.x, p.y);
-        return windowRect;
+        try {
+            // Use the home stack bounds
+            ActivityManager.StackInfo stackInfo = mIam.getStackInfo(ActivityManager.HOME_STACK_ID);
+            if (stackInfo != null) {
+                windowRect.set(stackInfo.bounds);
+            }
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        } finally {
+            return windowRect;
+        }
     }
 
     /** Starts an activity from recents. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index b8015c0..649cb4d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -20,12 +20,10 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
-import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 
@@ -63,7 +61,7 @@
     SystemServicesProxy mSystemServicesProxy;
 
     List<ActivityManager.RecentTaskInfo> mRawTasks;
-    SparseArray<TaskStack> mStacks = new SparseArray<>();
+    TaskStack mStack;
     HashMap<Task.ComponentNameKey, ActivityInfoHandle> mActivityInfoCache =
             new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
 
@@ -96,11 +94,8 @@
         // This activity info cache will be used for both preloadPlan() and executePlan()
         mActivityInfoCache.clear();
 
-        // TODO (multi-display): Currently assume the primary display
-        Rect displayBounds = mSystemServicesProxy.getWindowRect();
-
         Resources res = mContext.getResources();
-        SparseArray<ArrayList<Task>> stacksTasks = new SparseArray<>();
+        ArrayList<Task> stackTasks = new ArrayList<>();
         if (mRawTasks == null) {
             preloadRawTasks(isTopTaskHome);
         }
@@ -152,37 +147,13 @@
             task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false);
             if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
 
-            if (!mConfig.multiStackEnabled ||
-                    Constants.DebugFlags.App.EnableMultiStackToSingleStack) {
-                int firstStackId = 0;
-                ArrayList<Task> stackTasks = stacksTasks.get(firstStackId);
-                if (stackTasks == null) {
-                    stackTasks = new ArrayList<>();
-                    stacksTasks.put(firstStackId, stackTasks);
-                }
-                stackTasks.add(task);
-            } else {
-                ArrayList<Task> stackTasks = stacksTasks.get(t.stackId);
-                if (stackTasks == null) {
-                    stackTasks = new ArrayList<>();
-                    stacksTasks.put(t.stackId, stackTasks);
-                }
-                stackTasks.add(task);
-            }
+            stackTasks.add(task);
         }
 
         // Initialize the stacks
-        mStacks.clear();
-        int stackCount = stacksTasks.size();
-        for (int i = 0; i < stackCount; i++) {
-            int stackId = stacksTasks.keyAt(i);
-            ArrayList<Task> stackTasks = stacksTasks.valueAt(i);
-            TaskStack stack = new TaskStack(stackId);
-            stack.setBounds(displayBounds, displayBounds);
-            stack.setTasks(stackTasks);
-            stack.createAffiliatedGroupings(mConfig);
-            mStacks.put(stackId, stack);
-        }
+        mStack = new TaskStack();
+        mStack.setTasks(stackTasks);
+        mStack.createAffiliatedGroupings(mConfig);
     }
 
     /**
@@ -197,92 +168,70 @@
         Resources res = mContext.getResources();
 
         // Iterate through each of the tasks and load them according to the load conditions.
-        int stackCount = mStacks.size();
-        for (int j = 0; j < stackCount; j++) {
-            ArrayList<Task> tasks = mStacks.valueAt(j).getTasks();
-            int taskCount = tasks.size();
-            for (int i = 0; i < taskCount; i++) {
-                ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
-                Task task = tasks.get(i);
-                Task.TaskKey taskKey = task.key;
+        ArrayList<Task> tasks = mStack.getTasks();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
+            Task task = tasks.get(i);
+            Task.TaskKey taskKey = task.key;
 
-                // Get an existing activity info handle if possible
-                Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
-                ActivityInfoHandle infoHandle;
-                boolean hadCachedActivityInfo = false;
-                if (mActivityInfoCache.containsKey(cnKey)) {
-                    infoHandle = mActivityInfoCache.get(cnKey);
-                    hadCachedActivityInfo = true;
-                } else {
-                    infoHandle = new ActivityInfoHandle();
+            // Get an existing activity info handle if possible
+            Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
+            ActivityInfoHandle infoHandle;
+            boolean hadCachedActivityInfo = false;
+            if (mActivityInfoCache.containsKey(cnKey)) {
+                infoHandle = mActivityInfoCache.get(cnKey);
+                hadCachedActivityInfo = true;
+            } else {
+                infoHandle = new ActivityInfoHandle();
+            }
+
+            boolean isRunningTask = (task.key.id == opts.runningTaskId);
+            boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
+            boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
+
+            // If requested, skip the running task
+            if (opts.onlyLoadPausedActivities && isRunningTask) {
+                continue;
+            }
+
+            if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
+                if (task.activityIcon == null) {
+                    if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
+                    task.activityIcon = loader.getAndUpdateActivityIcon(taskKey,
+                            t.taskDescription, mSystemServicesProxy, res, infoHandle, true);
                 }
-
-                boolean isRunningTask = (task.key.id == opts.runningTaskId);
-                boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
-                boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
-
-                // If requested, skip the running task
-                if (opts.onlyLoadPausedActivities && isRunningTask) {
-                    continue;
-                }
-
-                if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
-                    if (task.activityIcon == null) {
-                        if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
-                        task.activityIcon = loader.getAndUpdateActivityIcon(taskKey,
-                                t.taskDescription, mSystemServicesProxy, res, infoHandle, true);
+            }
+            if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
+                if (task.thumbnail == null || isRunningTask) {
+                    if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
+                    if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
+                        task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
+                                mSystemServicesProxy, true);
+                    } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
+                        loadQueue.addTask(task);
                     }
                 }
-                if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
-                    if (task.thumbnail == null || isRunningTask) {
-                        if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
-                        if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
-                            task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
-                                    mSystemServicesProxy, true);
-                        } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
-                            loadQueue.addTask(task);
-                        }
-                    }
-                }
+            }
 
-                // Update the activity info cache
-                if (!hadCachedActivityInfo && infoHandle.info != null) {
-                    mActivityInfoCache.put(cnKey, infoHandle);
-                }
+            // Update the activity info cache
+            if (!hadCachedActivityInfo && infoHandle.info != null) {
+                mActivityInfoCache.put(cnKey, infoHandle);
             }
         }
     }
 
     /**
-     * Returns all TaskStacks from the preloaded list of recent tasks.
+     * Returns the TaskStack from the preloaded list of recent tasks.
      */
-    public ArrayList<TaskStack> getAllTaskStacks() {
-        ArrayList<TaskStack> stacks = new ArrayList<TaskStack>();
-        int stackCount = mStacks.size();
-        for (int i = 0; i < stackCount; i++) {
-            stacks.add(mStacks.valueAt(i));
-        }
-        // Ensure that we have at least one stack
-        if (stacks.isEmpty()) {
-            stacks.add(new TaskStack());
-        }
-        return stacks;
-    }
-
-    /**
-     * Returns a specific TaskStack from the preloaded list of recent tasks.
-     */
-    public TaskStack getTaskStack(int stackId) {
-        return mStacks.get(stackId);
+    public TaskStack getTaskStack() {
+        return mStack;
     }
 
     /** Returns whether there are any tasks in any stacks. */
     public boolean hasTasks() {
-        int stackCount = mStacks.size();
-        for (int i = 0; i < stackCount; i++) {
-            if (mStacks.valueAt(i).getTaskCount() > 0) {
-                return true;
-            }
+        if (mStack != null) {
+            return mStack.getTaskCount() > 0;
         }
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 5aaea15..a760a41 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -177,35 +177,17 @@
     // The task offset to apply to a task id as a group affiliation
     static final int IndividualTaskIdOffset = 1 << 16;
 
-    public final int id;
-    public final Rect stackBounds = new Rect();
-    public final Rect displayBounds = new Rect();
-
     FilteredTaskList mTaskList = new FilteredTaskList();
     TaskStackCallbacks mCb;
 
     ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>();
     HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>();
 
-    public TaskStack() {
-        this(0);
-    }
-
-    public TaskStack(int stackId) {
-        id = stackId;
-    }
-
     /** Sets the callbacks for this task stack. */
     public void setCallbacks(TaskStackCallbacks cb) {
         mCb = cb;
     }
 
-    /** Sets the bounds of this stack. */
-    public void setBounds(Rect stackBounds, Rect displayBounds) {
-        this.stackBounds.set(stackBounds);
-        this.displayBounds.set(displayBounds);
-    }
-
     /** Resets this TaskStack. */
     public void reset() {
         mCb = null;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 651b29a..c7d1dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -79,10 +79,9 @@
     RecentsConfiguration mConfig;
     LayoutInflater mInflater;
     DebugOverlayView mDebugOverlay;
-    RecentsViewLayoutAlgorithm mLayoutAlgorithm;
 
     ArrayList<TaskStack> mStacks;
-    List<TaskStackView> mTaskStackViews = new ArrayList<>();
+    TaskStackView mTaskStackView;
     RecentsAppWidgetHostView mSearchBar;
     RecentsViewCallbacks mCb;
 
@@ -102,7 +101,6 @@
         super(context, attrs, defStyleAttr, defStyleRes);
         mConfig = RecentsConfiguration.getInstance();
         mInflater = LayoutInflater.from(context);
-        mLayoutAlgorithm = new RecentsViewLayoutAlgorithm(mConfig);
     }
 
     /** Sets the callbacks */
@@ -116,61 +114,41 @@
     }
 
     /** Set/get the bsp root node */
-    public void setTaskStacks(ArrayList<TaskStack> stacks) {
-        int numStacks = stacks.size();
-
-        // Remove all/extra stack views
-        int numTaskStacksToKeep = 0; // Keep no tasks if we are recreating the layout
+    public void setTaskStack(TaskStack stack) {
         if (mConfig.launchedReuseTaskStackViews) {
-            numTaskStacksToKeep = Math.min(mTaskStackViews.size(), numStacks);
-        }
-        for (int i = mTaskStackViews.size() - 1; i >= numTaskStacksToKeep; i--) {
-            removeView(mTaskStackViews.remove(i));
-        }
-
-        // Update the stack views that we are keeping
-        for (int i = 0; i < numTaskStacksToKeep; i++) {
-            TaskStackView tsv = mTaskStackViews.get(i);
-            // If onRecentsHidden is not triggered, we need to the stack view again here
-            tsv.reset();
-            tsv.setStack(stacks.get(i));
-        }
-
-        // Add remaining/recreate stack views
-        mStacks = stacks;
-        for (int i = mTaskStackViews.size(); i < numStacks; i++) {
-            TaskStack stack = stacks.get(i);
-            TaskStackView stackView = new TaskStackView(getContext(), stack);
-            stackView.setCallbacks(this);
-            addView(stackView);
-            mTaskStackViews.add(stackView);
+            if (mTaskStackView != null) {
+                // If onRecentsHidden is not triggered, we need to the stack view again here
+                mTaskStackView.reset();
+                mTaskStackView.setStack(stack);
+            } else {
+                mTaskStackView = new TaskStackView(getContext(), stack);
+                mTaskStackView.setCallbacks(this);
+                addView(mTaskStackView);
+            }
+        } else {
+            if (mTaskStackView != null) {
+                removeView(mTaskStackView);
+            }
+            mTaskStackView = new TaskStackView(getContext(), stack);
+            mTaskStackView.setCallbacks(this);
+            addView(mTaskStackView);
         }
 
         // Enable debug mode drawing on all the stacks if necessary
         if (mConfig.debugModeEnabled) {
-            for (int i = mTaskStackViews.size() - 1; i >= 0; i--) {
-                TaskStackView stackView = mTaskStackViews.get(i);
-                stackView.setDebugOverlay(mDebugOverlay);
-            }
+            mTaskStackView.setDebugOverlay(mDebugOverlay);
         }
 
         // Trigger a new layout
         requestLayout();
     }
 
-    /** Gets the list of task views */
-    List<TaskStackView> getTaskStackViews() {
-        return mTaskStackViews;
-    }
-
     /** Gets the next task in the stack - or if the last - the top task */
     public Task getNextTaskOrTopTask(Task taskToSearch) {
         Task returnTask = null;
         boolean found = false;
-        List<TaskStackView> stackViews = getTaskStackViews();
-        int stackCount = stackViews.size();
-        for (int i = stackCount - 1; i >= 0; --i) {
-            TaskStack stack = stackViews.get(i).getStack();
+        if (mTaskStackView != null) {
+            TaskStack stack = mTaskStackView.getStack();
             ArrayList<Task> taskList = stack.getTasks();
             // Iterate the stack views and try and find the focused task
             for (int j = taskList.size() - 1; j >= 0; --j) {
@@ -190,20 +168,16 @@
 
     /** Launches the focused task from the first stack if possible */
     public boolean launchFocusedTask() {
-        // Get the first stack view
-        List<TaskStackView> stackViews = getTaskStackViews();
-        int stackCount = stackViews.size();
-        for (int i = 0; i < stackCount; i++) {
-            TaskStackView stackView = stackViews.get(i);
-            TaskStack stack = stackView.getStack();
+        if (mTaskStackView != null) {
+            TaskStack stack = mTaskStackView.getStack();
             // Iterate the stack views and try and find the focused task
-            List<TaskView> taskViews = stackView.getTaskViews();
+            List<TaskView> taskViews = mTaskStackView.getTaskViews();
             int taskViewCount = taskViews.size();
             for (int j = 0; j < taskViewCount; j++) {
                 TaskView tv = taskViews.get(j);
                 Task task = tv.getTask();
                 if (tv.isFocusedTask()) {
-                    onTaskViewClicked(stackView, tv, stack, task, false, false, null);
+                    onTaskViewClicked(mTaskStackView, tv, stack, task, false, false, null);
                     return true;
                 }
             }
@@ -213,19 +187,15 @@
 
     /** Launches a given task. */
     public boolean launchTask(Task task, Rect taskBounds) {
-        // Get the first stack view
-        List<TaskStackView> stackViews = getTaskStackViews();
-        int stackCount = stackViews.size();
-        for (int i = 0; i < stackCount; i++) {
-            TaskStackView stackView = stackViews.get(i);
-            TaskStack stack = stackView.getStack();
+        if (mTaskStackView != null) {
+            TaskStack stack = mTaskStackView.getStack();
             // Iterate the stack views and try and find the given task.
-            List<TaskView> taskViews = stackView.getTaskViews();
+            List<TaskView> taskViews = mTaskStackView.getTaskViews();
             int taskViewCount = taskViews.size();
             for (int j = 0; j < taskViewCount; j++) {
                 TaskView tv = taskViews.get(j);
                 if (tv.getTask() == task) {
-                    onTaskViewClicked(stackView, tv, stack, task, false, true, taskBounds);
+                    onTaskViewClicked(mTaskStackView, tv, stack, task, false, true, taskBounds);
                     return true;
                 }
             }
@@ -235,12 +205,8 @@
 
     /** Launches the task that Recents was launched from, if possible */
     public boolean launchPreviousTask() {
-        // Get the first stack view
-        List<TaskStackView> stackViews = getTaskStackViews();
-        int stackCount = stackViews.size();
-        for (int i = 0; i < stackCount; i++) {
-            TaskStackView stackView = stackViews.get(i);
-            TaskStack stack = stackView.getStack();
+        if (mTaskStackView != null) {
+            TaskStack stack = mTaskStackView.getStack();
             ArrayList<Task> tasks = stack.getTasks();
 
             // Find the launch task in the stack
@@ -249,8 +215,8 @@
                 for (int j = 0; j < taskCount; j++) {
                     if (tasks.get(j).isLaunchTarget) {
                         Task task = tasks.get(j);
-                        TaskView tv = stackView.getChildViewForTask(task);
-                        onTaskViewClicked(stackView, tv, stack, task, false, false, null);
+                        TaskView tv = mTaskStackView.getChildViewForTask(task);
+                        onTaskViewClicked(mTaskStackView, tv, stack, task, false, false, null);
                         return true;
                     }
                 }
@@ -264,12 +230,8 @@
         // We have to increment/decrement the post animation trigger in case there are no children
         // to ensure that it runs
         ctx.postAnimationTrigger.increment();
-
-        List<TaskStackView> stackViews = getTaskStackViews();
-        int stackCount = stackViews.size();
-        for (int i = 0; i < stackCount; i++) {
-            TaskStackView stackView = stackViews.get(i);
-            stackView.startEnterRecentsAnimation(ctx);
+        if (mTaskStackView != null) {
+            mTaskStackView.startEnterRecentsAnimation(ctx);
         }
         ctx.postAnimationTrigger.decrement();
     }
@@ -279,11 +241,8 @@
         // We have to increment/decrement the post animation trigger in case there are no children
         // to ensure that it runs
         ctx.postAnimationTrigger.increment();
-        List<TaskStackView> stackViews = getTaskStackViews();
-        int stackCount = stackViews.size();
-        for (int i = 0; i < stackCount; i++) {
-            TaskStackView stackView = stackViews.get(i);
-            stackView.startExitToHomeAnimation(ctx);
+        if (mTaskStackView != null) {
+            mTaskStackView.startExitToHomeAnimation(ctx);
         }
         ctx.postAnimationTrigger.decrement();
 
@@ -329,31 +288,19 @@
         // Get the search bar bounds and measure the search bar layout
         Rect searchBarSpaceBounds = new Rect();
         if (mSearchBar != null) {
-            mConfig.getSearchBarBounds(width, height, mConfig.systemInsets.top, searchBarSpaceBounds);
+            mConfig.getSearchBarBounds(new Rect(0, 0, width, height), mConfig.systemInsets.top,
+                    searchBarSpaceBounds);
             mSearchBar.measure(
                     MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
                     MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY));
         }
 
         Rect taskStackBounds = new Rect();
-        mConfig.getAvailableTaskStackBounds(width, height, mConfig.systemInsets.top,
+        mConfig.getAvailableTaskStackBounds(new Rect(0, 0, width, height), mConfig.systemInsets.top,
                 mConfig.systemInsets.right, searchBarSpaceBounds, taskStackBounds);
-
-        // Measure each TaskStackView with the full width and height of the window since the
-        // transition view is a child of that stack view
-        List<TaskStackView> stackViews = getTaskStackViews();
-        List<Rect> stackViewsBounds = mLayoutAlgorithm.computeStackRects(stackViews,
-                taskStackBounds);
-        int stackCount = stackViews.size();
-        for (int i = 0; i < stackCount; i++) {
-            TaskStackView stackView = stackViews.get(i);
-            if (stackView.getVisibility() != GONE) {
-                // We are going to measure the TaskStackView with the whole RecentsView dimensions,
-                // but the actual stack is going to be inset to the bounds calculated by the layout
-                // algorithm
-                stackView.setStackInsetRect(stackViewsBounds.get(i));
-                stackView.measure(widthMeasureSpec, heightMeasureSpec);
-            }
+        if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
+            mTaskStackView.setTaskStackBounds(taskStackBounds);
+            mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
         }
 
         setMeasuredDimension(width, height);
@@ -365,24 +312,17 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         // Get the search bar bounds so that we lay it out
+        Rect measuredRect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight());
+        Rect searchBarSpaceBounds = new Rect();
         if (mSearchBar != null) {
-            Rect searchBarSpaceBounds = new Rect();
-            mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(),
+            mConfig.getSearchBarBounds(measuredRect,
                     mConfig.systemInsets.top, searchBarSpaceBounds);
             mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top,
                     searchBarSpaceBounds.right, searchBarSpaceBounds.bottom);
         }
 
-        // Layout each TaskStackView with the full width and height of the window since the
-        // transition view is a child of that stack view
-        List<TaskStackView> stackViews = getTaskStackViews();
-        int stackCount = stackViews.size();
-        for (int i = 0; i < stackCount; i++) {
-            TaskStackView stackView = stackViews.get(i);
-            if (stackView.getVisibility() != GONE) {
-                stackView.layout(left, top, left + stackView.getMeasuredWidth(),
-                        top + stackView.getMeasuredHeight());
-            }
+        if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
+            mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
         }
     }
 
@@ -397,29 +337,24 @@
     /** Notifies each task view of the user interaction. */
     public void onUserInteraction() {
         // Get the first stack view
-        List<TaskStackView> stackViews = getTaskStackViews();
-        int stackCount = stackViews.size();
-        for (int i = 0; i < stackCount; i++) {
-            TaskStackView stackView = stackViews.get(i);
-            stackView.onUserInteraction();
+        if (mTaskStackView != null) {
+            mTaskStackView.onUserInteraction();
         }
     }
 
     /** Focuses the next task in the first stack view */
     public void focusNextTask(boolean forward) {
         // Get the first stack view
-        List<TaskStackView> stackViews = getTaskStackViews();
-        if (!stackViews.isEmpty()) {
-            stackViews.get(0).focusNextTask(forward, true);
+        if (mTaskStackView != null) {
+            mTaskStackView.focusNextTask(forward, true);
         }
     }
 
     /** Dismisses the focused task. */
     public void dismissFocusedTask() {
         // Get the first stack view
-        List<TaskStackView> stackViews = getTaskStackViews();
-        if (!stackViews.isEmpty()) {
-            stackViews.get(0).dismissFocusedTask();
+        if (mTaskStackView != null) {
+            mTaskStackView.dismissFocusedTask();
         }
     }
 
@@ -442,9 +377,8 @@
     }
 
     public void disableLayersForOneFrame() {
-        List<TaskStackView> stackViews = getTaskStackViews();
-        for (int i = 0; i < stackViews.size(); i++) {
-            stackViews.get(i).disableLayersForOneFrame();
+        if (mTaskStackView != null) {
+            mTaskStackView.disableLayersForOneFrame();
         }
     }
 
@@ -655,7 +589,7 @@
             }
             postDrawHeaderThumbnailTransitionRunnable(stackView, tv, offsetX, offsetY, stackScroll,
                     animStartedListener);
-            if (mConfig.multiStackEnabled) {
+            if (mConfig.multiWindowEnabled) {
                 opts = ActivityOptions.makeCustomAnimation(sourceView.getContext(),
                         R.anim.recents_from_unknown_enter,
                         R.anim.recents_from_unknown_exit,
@@ -767,11 +701,8 @@
     /** Final callback after Recents is finally hidden. */
     public void onRecentsHidden() {
         // Notify each task stack view
-        List<TaskStackView> stackViews = getTaskStackViews();
-        int stackCount = stackViews.size();
-        for (int i = 0; i < stackCount; i++) {
-            TaskStackView stackView = stackViews.get(i);
-            stackView.onRecentsHidden();
+        if (mTaskStackView != null) {
+            mTaskStackView.onRecentsHidden();
         }
     }
 
@@ -815,11 +746,8 @@
     @Override
     public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) {
         // Propagate this event down to each task stack view
-        List<TaskStackView> stackViews = getTaskStackViews();
-        int stackCount = stackViews.size();
-        for (int i = 0; i < stackCount; i++) {
-            TaskStackView stackView = stackViews.get(i);
-            stackView.onPackagesChanged(monitor, packageName, userId);
+        if (mTaskStackView != null) {
+            mTaskStackView.onPackagesChanged(monitor, packageName, userId);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java
deleted file mode 100644
index eea273c..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2014 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.systemui.recents.views;
-
-import android.graphics.Rect;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.model.TaskStack;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/* The layout logic for the RecentsView. */
-public class RecentsViewLayoutAlgorithm {
-
-    RecentsConfiguration mConfig;
-
-    public RecentsViewLayoutAlgorithm(RecentsConfiguration config) {
-        mConfig = config;
-    }
-
-    /** Return the relative coordinate given coordinates in another size. */
-    private int getRelativeCoordinate(int availableOffset, int availableSize, int otherCoord, int otherSize) {
-        float relPos = (float) otherCoord / otherSize;
-        return availableOffset + (int) (relPos * availableSize);
-    }
-
-    /**
-     * Computes and returns the bounds that each of the stack views should take up.
-     */
-    List<Rect> computeStackRects(List<TaskStackView> stackViews, Rect availableBounds) {
-        ArrayList<Rect> bounds = new ArrayList<Rect>(stackViews.size());
-        int stackViewsCount = stackViews.size();
-        for (int i = 0; i < stackViewsCount; i++) {
-            TaskStack stack = stackViews.get(i).getStack();
-            Rect sb = stack.stackBounds;
-            Rect db = stack.displayBounds;
-            Rect ab = availableBounds;
-            bounds.add(new Rect(getRelativeCoordinate(ab.left, ab.width(), sb.left, db.width()),
-                    getRelativeCoordinate(ab.top, ab.height(), sb.top, db.height()),
-                    getRelativeCoordinate(ab.left, ab.width(), sb.right, db.width()),
-                    getRelativeCoordinate(ab.top, ab.height(), sb.bottom, db.height())));
-        }
-        return bounds;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 4e82c8a..8058c5e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -80,7 +80,6 @@
     ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>();
     DozeTrigger mUIDozeTrigger;
     DebugOverlayView mDebugOverlay;
-    Rect mTaskStackBounds = new Rect();
     DismissView mDismissAllButton;
     boolean mDismissAllButtonAnimating;
     int mFocusedTaskIndex = -1;
@@ -93,6 +92,7 @@
     boolean mStartEnterAnimationRequestedAfterLayout;
     boolean mStartEnterAnimationCompleted;
     ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
+    Rect mTaskStackBounds = new Rect();
     int[] mTmpVisibleRange = new int[2];
     float[] mTmpCoord = new float[2];
     Matrix mTmpMatrix = new Matrix();
@@ -477,11 +477,6 @@
         mStackViewsClipDirty = false;
     }
 
-    /** The stack insets to apply to the stack contents */
-    public void setStackInsetRect(Rect r) {
-        mTaskStackBounds.set(r);
-    }
-
     /** Updates the min and max virtual scroll bounds */
     void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab,
             boolean launchedFromHome) {
@@ -692,11 +687,6 @@
         return mTouchHandler.onGenericMotionEvent(ev);
     }
 
-    /** Returns the region that touch gestures can be started in. */
-    Rect getTouchableRegion() {
-        return mTaskStackBounds;
-    }
-
     @Override
     public void computeScroll() {
         mStackScroller.computeScroll();
@@ -736,6 +726,10 @@
         return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
     }
 
+    public void setTaskStackBounds(Rect taskStackBounds) {
+        mTaskStackBounds.set(taskStackBounds);
+    }
+
     /**
      * This is called with the full window width and height to allow stack view children to
      * perform the full screen transition down.
@@ -746,9 +740,7 @@
         int height = MeasureSpec.getSize(heightMeasureSpec);
 
         // Compute our stack/task rects
-        Rect taskStackBounds = new Rect(mTaskStackBounds);
-        taskStackBounds.bottom -= mConfig.systemInsets.bottom;
-        computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab,
+        computeRects(width, height, mTaskStackBounds, mConfig.launchedWithAltTab,
                 mConfig.launchedFromHome);
 
         // If this is the first layout, then scroll to the front of the stack and synchronize the
@@ -875,7 +867,7 @@
         }
 
         // Start dozing
-        if (!mConfig.multiStackEnabled) {
+        if (!mConfig.multiWindowEnabled) {
             mUIDozeTrigger.startDozing();
         }
     }
@@ -1299,7 +1291,7 @@
         RecentsTaskLoader.getInstance().loadTaskData(task);
 
         // If the doze trigger has already fired, then update the state for this task view
-        if (mConfig.multiStackEnabled || mUIDozeTrigger.hasTriggered()) {
+        if (mConfig.multiWindowEnabled || mUIDozeTrigger.hasTriggered()) {
             tv.setNoUserInteractionState();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 2e0b80a..7d079d9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -128,16 +128,6 @@
             return false;
         }
 
-        int action = ev.getAction();
-        if (mConfig.multiStackEnabled) {
-            // Check if we are within the bounds of the stack view contents
-            if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
-                if (!mSv.getTouchableRegion().contains((int) ev.getX(), (int) ev.getY())) {
-                    return false;
-                }
-            }
-        }
-
         // Pass through to swipe helper if we are swiping
         mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
         if (mInterceptedBySwipeHelper) {
@@ -146,6 +136,7 @@
 
         boolean wasScrolling = mScroller.isScrolling() ||
                 (mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning());
+        int action = ev.getAction();
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
                 // Save the touch down info
@@ -212,16 +203,6 @@
             return false;
         }
 
-        int action = ev.getAction();
-        if (mConfig.multiStackEnabled) {
-            // Check if we are within the bounds of the stack view contents
-            if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
-                if (!mSv.getTouchableRegion().contains((int) ev.getX(), (int) ev.getY())) {
-                    return false;
-                }
-            }
-        }
-
         // Pass through to swipe helper if we are swiping
         if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
             return true;
@@ -230,6 +211,7 @@
         // Update the velocity tracker
         initVelocityTrackerIfNotExists();
 
+        int action = ev.getAction();
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
                 // Save the touch down info
@@ -372,7 +354,7 @@
         // Shift the tap position toward the center of the task stack and check to see if it would
         // have hit a view. The user might have tried to tap on a task and missed slightly.
         int shiftedX = x;
-        if (x > mSv.getTouchableRegion().centerX()) {
+        if (x > (mSv.getRight() - mSv.getLeft()) / 2) {
             shiftedX -= mWindowTouchSlop;
         } else {
             shiftedX += mWindowTouchSlop;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index cbfe842..373fe7b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -682,7 +682,7 @@
                 mHeaderView.mApplicationIcon.setOnClickListener(this);
             }
             mHeaderView.mDismissButton.setOnClickListener(this);
-            if (mConfig.multiStackEnabled) {
+            if (mConfig.multiWindowEnabled) {
                 mHeaderView.mMoveTaskButton.setOnClickListener(this);
             }
             mActionButtonView.setOnClickListener(this);
@@ -701,7 +701,7 @@
             // Unbind any listeners
             mHeaderView.mApplicationIcon.setOnClickListener(null);
             mHeaderView.mDismissButton.setOnClickListener(null);
-            if (mConfig.multiStackEnabled) {
+            if (mConfig.multiWindowEnabled) {
                 mHeaderView.mMoveTaskButton.setOnClickListener(null);
             }
             mActionButtonView.setOnClickListener(null);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 353bcbe..3e9410e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -215,8 +215,8 @@
                 mLightDismissDrawable : mDarkDismissDrawable);
         mDismissButton.setContentDescription(String.format(mDismissContentDescription,
                 t.contentDescription));
-        mMoveTaskButton.setVisibility((mConfig.multiStackEnabled) ? View.VISIBLE : View.INVISIBLE);
-        if (mConfig.multiStackEnabled) {
+        mMoveTaskButton.setVisibility((mConfig.multiWindowEnabled) ? View.VISIBLE : View.INVISIBLE);
+        if (mConfig.multiWindowEnabled) {
             updateResizeTaskBarIcon(t);
         }
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
index 2e6136f..8989625 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
@@ -16,9 +16,16 @@
 
 package com.android.server.accessibility;
 
+import android.annotation.NonNull;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -46,16 +53,17 @@
  * the class should handle cases where multiple mouse devices are present.
  */
 public class AutoclickController implements EventStreamTransformation {
+
+    public static final int DEFAULT_CLICK_DELAY_MS = 600;
+
     private static final String LOG_TAG = AutoclickController.class.getSimpleName();
 
-    // TODO: Control click delay via settings.
-    private static final int CLICK_DELAY_MS = 600;
-
     private EventStreamTransformation mNext;
-    private Context mContext;
+    private final Context mContext;
 
     // Lazily created on the first mouse motion event.
     private ClickScheduler mClickScheduler;
+    private ClickDelayObserver mClickDelayObserver;
 
     public AutoclickController(Context context) {
         mContext = context;
@@ -66,7 +74,9 @@
         if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
             if (mClickScheduler == null) {
                 Handler handler = new Handler(mContext.getMainLooper());
-                mClickScheduler = new ClickScheduler(handler, CLICK_DELAY_MS);
+                mClickScheduler = new ClickScheduler(handler, DEFAULT_CLICK_DELAY_MS);
+                mClickDelayObserver = new ClickDelayObserver(handler);
+                mClickDelayObserver.start(mContext.getContentResolver(), mClickScheduler);
             }
 
             handleMouseMotion(event, policyFlags);
@@ -119,8 +129,13 @@
 
     @Override
     public void onDestroy() {
+        if (mClickDelayObserver != null) {
+            mClickDelayObserver.stop();
+            mClickDelayObserver = null;
+        }
         if (mClickScheduler != null) {
             mClickScheduler.cancel();
+            mClickScheduler = null;
         }
     }
 
@@ -143,6 +158,80 @@
     }
 
     /**
+     * Observes setting value for autoclick delay, and updates ClickScheduler delay whenever the
+     * setting value changes.
+     */
+    final private static class ClickDelayObserver extends ContentObserver {
+        /** URI used to identify the autoclick delay setting with content resolver. */
+        private final Uri mAutoclickDelaySettingUri = Settings.Secure.getUriFor(
+                Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY);
+
+        private ContentResolver mContentResolver;
+        private ClickScheduler mClickScheduler;
+
+        public ClickDelayObserver(Handler handler) {
+            super(handler);
+        }
+
+        /**
+         * Starts the observer. And makes sure up-to-date autoclick delay is propagated to
+         * |clickScheduler|.
+         *
+         * @param contentResolver Content resolver that should be observed for setting's value
+         *     changes.
+         * @param clickScheduler ClickScheduler that should be updated when click delay changes.
+         * @throws IllegalStateException If internal state is already setup when the method is
+         *         called.
+         * @throws NullPointerException If any of the arguments is a null pointer.
+         */
+        public void start(@NonNull ContentResolver contentResolver,
+                @NonNull ClickScheduler clickScheduler) {
+            if (mContentResolver != null || mClickScheduler != null) {
+                throw new IllegalStateException("Observer already started.");
+            }
+            if (contentResolver == null) {
+                throw new NullPointerException("contentResolver not set.");
+            }
+            if (clickScheduler == null) {
+                throw new NullPointerException("clickScheduler not set.");
+            }
+
+            mContentResolver = contentResolver;
+            mClickScheduler = clickScheduler;
+            mContentResolver.registerContentObserver(mAutoclickDelaySettingUri, false, this,
+                    UserHandle.USER_ALL);
+
+            // Initialize mClickScheduler's initial delay value.
+            onChange(true, mAutoclickDelaySettingUri);
+        }
+
+        /**
+         * Stops the the observer. Should only be called if the observer has been started.
+         *
+         * @throws IllegalStateException If internal state hasn't yet been initialized by calling
+         *         {@link #start}.
+         */
+        public void stop() {
+            if (mContentResolver == null || mClickScheduler == null) {
+                throw new IllegalStateException("ClickDelayObserver not started.");
+            }
+
+            mContentResolver.unregisterContentObserver(this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (mAutoclickDelaySettingUri.equals(uri)) {
+                // TODO: Plumb current user id down to here and use getIntForUser.
+                int delay = Settings.Secure.getInt(
+                        mContentResolver, Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
+                        DEFAULT_CLICK_DELAY_MS);
+                mClickScheduler.updateDelay(delay);
+            }
+        }
+    }
+
+    /**
      * Schedules and performs click event sequence that should be initiated when mouse pointer stops
      * moving. The click is first scheduled when a mouse movement is detected, and then further
      * delayed on every sufficient mouse movement.
@@ -242,6 +331,15 @@
         }
 
         /**
+         * Updates delay that should be used when scheduling clicks. The delay will be used only for
+         * clicks scheduled after this point (pending click tasks are not affected).
+         * @param delay New delay value.
+         */
+        public void updateDelay(int delay) {
+            mDelay = delay;
+        }
+
+        /**
          * Updates the time at which click sequence should occur.
          *
          * @param delay Delay (from now) after which click should occur.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b21e9027..372a5fa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -321,6 +321,9 @@
     // How long we wait for a launched process to attach to the activity manager
     // before we decide it's never going to come up for real.
     static final int PROC_START_TIMEOUT = 10*1000;
+    // How long we wait for an attached process to publish its content providers
+    // before we decide it must be hung.
+    static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000;
 
     // How long we wait for a launched process to attach to the activity manager
     // before we decide it's never going to come up for real, when the process was
@@ -1378,6 +1381,7 @@
     static final int REPORT_USER_SWITCH_COMPLETE_MSG = 56;
     static final int SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG = 57;
     static final int APP_BOOST_DEACTIVATE_MSG = 58;
+    static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 59;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1714,6 +1718,12 @@
                     processStartTimedOutLocked(app);
                 }
             } break;
+            case CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG: {
+                ProcessRecord app = (ProcessRecord)msg.obj;
+                synchronized (ActivityManagerService.this) {
+                    processContentProviderPublishTimedOutLocked(app);
+                }
+            } break;
             case DO_PENDING_ACTIVITY_LAUNCHES_MSG: {
                 synchronized (ActivityManagerService.this) {
                     mStackSupervisor.doPendingActivityLaunchesLocked(true);
@@ -5962,6 +5972,11 @@
         return needRestart;
     }
 
+    private final void processContentProviderPublishTimedOutLocked(ProcessRecord app) {
+        cleanupAppInLaunchingProvidersLocked(app, true);
+        removeProcessLocked(app, false, true, "timeout publishing content providers");
+    }
+
     private final void processStartTimedOutLocked(ProcessRecord app) {
         final int pid = app.pid;
         boolean gone = false;
@@ -5988,7 +6003,7 @@
                 mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
             }
             // Take care of any launching providers waiting for this process.
-            checkAppInLaunchingProvidersLocked(app, true);
+            cleanupAppInLaunchingProvidersLocked(app, true);
             // Take care of any services that are waiting for the process.
             mServices.processStartTimedOutLocked(app);
             app.kill("start timeout", true);
@@ -6084,6 +6099,12 @@
         boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
         List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;
 
+        if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
+            Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
+            msg.obj = app;
+            mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
+        }
+
         if (!normalMode) {
             Slog.i(TAG, "Launching preboot mode app: " + app);
         }
@@ -10049,7 +10070,7 @@
             final long origId = Binder.clearCallingIdentity();
 
             final int N = providers.size();
-            for (int i=0; i<N; i++) {
+            for (int i = 0; i < N; i++) {
                 ContentProviderHolder src = providers.get(i);
                 if (src == null || src.info == null || src.provider == null) {
                     continue;
@@ -10064,15 +10085,20 @@
                         mProviderMap.putProviderByName(names[j], dst);
                     }
 
-                    int NL = mLaunchingProviders.size();
+                    int launchingCount = mLaunchingProviders.size();
                     int j;
-                    for (j=0; j<NL; j++) {
+                    boolean wasInLaunchingProviders = false;
+                    for (j = 0; j < launchingCount; j++) {
                         if (mLaunchingProviders.get(j) == dst) {
                             mLaunchingProviders.remove(j);
+                            wasInLaunchingProviders = true;
                             j--;
-                            NL--;
+                            launchingCount--;
                         }
                     }
+                    if (wasInLaunchingProviders) {
+                        mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
+                    }
                     synchronized (dst) {
                         dst.provider = src.provider;
                         dst.proc = r;
@@ -15743,7 +15769,7 @@
         app.pubProviders.clear();
 
         // Take care of any launching providers waiting for this process.
-        if (checkAppInLaunchingProvidersLocked(app, false)) {
+        if (cleanupAppInLaunchingProvidersLocked(app, false)) {
             restart = true;
         }
 
@@ -15865,7 +15891,17 @@
         return false;
     }
 
-    boolean checkAppInLaunchingProvidersLocked(ProcessRecord app, boolean alwaysBad) {
+    boolean checkAppInLaunchingProvidersLocked(ProcessRecord app) {
+        for (int i = mLaunchingProviders.size() - 1; i >= 0; i--) {
+            ContentProviderRecord cpr = mLaunchingProviders.get(i);
+            if (cpr.launchingApp == app) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean cleanupAppInLaunchingProvidersLocked(ProcessRecord app, boolean alwaysBad) {
         // Look through the content providers we are waiting to have launched,
         // and if any run in this process then either schedule a restart of
         // the process or kill the client waiting for it if this process has
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d3cea8d..0a3391c 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3061,7 +3061,12 @@
             return;
         }
 
-        if (task.mBounds != null && task.mBounds.equals(bounds)) {
+        // TODO: change resizedByUser to an enum (or bitmask?) to indicate the origin of
+        //       this resize (eg. systemResize, userResize, forcedResized).
+        // If the resize is a drag-resize by user, let it go through even if the bounds
+        // is not changing, as we might need a relayout due to surface size change
+        // (to/from fullscreen).
+        if (task.mBounds != null && task.mBounds.equals(bounds) && !resizedByUser) {
             // Nothing to do here...
             return;
         }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 335288d..62768c3 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -171,12 +171,11 @@
     
     public void publish(Context context) {
         mContext = context;
-        ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
-        mStats.setNumSpeedSteps(new PowerProfile(mContext).getNumSpeedSteps());
         mStats.setRadioScanningTimeout(mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_radioScanningTimeout)
                 * 1000L);
         mStats.setPowerProfile(new PowerProfile(context));
+        ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 6ba25a5..701b9f1 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -49,6 +49,13 @@
      */
     private static final AtomicInteger NEXT_DISPLAY_MODE_ID = new AtomicInteger(1);  // 0 = no mode.
 
+    /**
+     * Used to generate globally unique color transform ids.
+     *
+     * Valid IDs start at 1 with 0 as the sentinel value for the default mode.
+     */
+    private static final AtomicInteger NEXT_COLOR_TRANSFORM_ID = new AtomicInteger(1);
+
     // Called with SyncRoot lock held.
     public DisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
             Context context, Handler handler, Listener listener, String name) {
@@ -134,6 +141,11 @@
                 NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate);
     }
 
+    public static Display.ColorTransform createColorTransform(int colorTransform) {
+        return new Display.ColorTransform(
+                NEXT_COLOR_TRANSFORM_ID.getAndIncrement(), colorTransform);
+    }
+
     public interface Listener {
         public void onDisplayDeviceEvent(DisplayDevice device, int event);
         public void onTraversalRequested();
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 93bda46..7af0bdb 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -135,7 +135,7 @@
     /**
      * Sets the mode, if supported.
      */
-    public void requestModeInTransactionLocked(int id) {
+    public void requestColorTransformAndModeInTransactionLocked(int colorTransformId, int modeId) {
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 97ada15..55ba302 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -155,6 +155,15 @@
      */
     public Display.Mode[] supportedModes = Display.Mode.EMPTY_ARRAY;
 
+    /** The active color transform of the display */
+    public int colorTransformId;
+
+    /** The default color transform of the display */
+    public int defaultColorTransformId;
+
+    /** The supported color transforms of the display */
+    public Display.ColorTransform[] supportedColorTransforms = Display.ColorTransform.EMPTY_ARRAY;
+
     /**
      * The nominal apparent density of the display in DPI used for layout calculations.
      * This density is sensitive to the viewing distance.  A big TV and a tablet may have
@@ -276,6 +285,9 @@
                 || modeId != other.modeId
                 || defaultModeId != other.defaultModeId
                 || !Arrays.equals(supportedModes, other.supportedModes)
+                || colorTransformId != other.colorTransformId
+                || defaultColorTransformId != other.defaultColorTransformId
+                || !Arrays.equals(supportedColorTransforms, other.supportedColorTransforms)
                 || densityDpi != other.densityDpi
                 || xDpi != other.xDpi
                 || yDpi != other.yDpi
@@ -306,6 +318,9 @@
         modeId = other.modeId;
         defaultModeId = other.defaultModeId;
         supportedModes = other.supportedModes;
+        colorTransformId = other.colorTransformId;
+        defaultColorTransformId = other.defaultColorTransformId;
+        supportedColorTransforms = other.supportedColorTransforms;
         densityDpi = other.densityDpi;
         xDpi = other.xDpi;
         yDpi = other.yDpi;
@@ -331,6 +346,9 @@
         sb.append(", modeId ").append(modeId);
         sb.append(", defaultModeId ").append(defaultModeId);
         sb.append(", supportedModes ").append(Arrays.toString(supportedModes));
+        sb.append(", colorTransformId ").append(colorTransformId);
+        sb.append(", defaultColorTransformId ").append(defaultColorTransformId);
+        sb.append(", supportedColorTransforms ").append(Arrays.toString(supportedColorTransforms));
         sb.append(", density ").append(densityDpi);
         sb.append(", ").append(xDpi).append(" x ").append(yDpi).append(" dpi");
         sb.append(", appVsyncOff ").append(appVsyncOffsetNanos);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b2ab797..6a6570b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -540,6 +540,17 @@
         }
     }
 
+    private void requestColorTransformInternal(int displayId, int colorTransformId) {
+        synchronized (mSyncRoot) {
+            LogicalDisplay display = mLogicalDisplays.get(displayId);
+            if (display != null &&
+                    display.getRequestedColorTransformIdLocked() != colorTransformId) {
+                display.setRequestedColorTransformIdLocked(colorTransformId);
+                scheduleTraversalLocked(false);
+            }
+        }
+    }
+
     private int createVirtualDisplayInternal(IVirtualDisplayCallback callback,
             IMediaProjection projection, int callingUid, String packageName,
             String name, int width, int height, int densityDpi, Surface surface, int flags) {
@@ -1340,6 +1351,19 @@
         }
 
         @Override // Binder call
+        public void requestColorTransform(int displayId, int colorTransformId) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.CONFIGURE_DISPLAY_COLOR_TRANSFORM,
+                    "Permission required to change the display color transform");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                requestColorTransformInternal(displayId, colorTransformId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
         public int createVirtualDisplay(IVirtualDisplayCallback callback,
                 IMediaProjection projection, String packageName, String name,
                 int width, int height, int densityDpi, Surface surface, int flags) {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 517a825..be37f52 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -31,6 +31,7 @@
 import android.os.Trace;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.Display;
 import android.view.DisplayEventReceiver;
 import android.view.Surface;
@@ -38,6 +39,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * A display adapter for the local displays managed by Surface Flinger.
@@ -143,14 +145,22 @@
         private final int mBuiltInDisplayId;
         private final Light mBacklight;
         private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();
+        private final SparseArray<Display.ColorTransform> mSupportedColorTransforms =
+                new SparseArray<>();
 
         private DisplayDeviceInfo mInfo;
         private boolean mHavePendingChanges;
         private int mState = Display.STATE_UNKNOWN;
         private int mBrightness = PowerManager.BRIGHTNESS_DEFAULT;
+        private int mActivePhysIndex;
         private int mDefaultModeId;
         private int mActiveModeId;
         private boolean mActiveModeInvalid;
+        private int mDefaultColorTransformId;
+        private int mActiveColorTransformId;
+        private boolean mActiveColorTransformInvalid;
+
+        private  SurfaceControl.PhysicalDisplayInfo mDisplayInfos[];
 
         public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId,
                 SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo) {
@@ -167,22 +177,73 @@
 
         public boolean updatePhysicalDisplayInfoLocked(
                 SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo) {
-            // Build an updated list of all existing modes.
-            boolean modesAdded = false;
-            DisplayModeRecord activeRecord = null;
-            ArrayList<DisplayModeRecord> records = new ArrayList<DisplayModeRecord>();
+            mDisplayInfos = Arrays.copyOf(physicalDisplayInfos, physicalDisplayInfos.length);
+            mActivePhysIndex = activeDisplayInfo;
+            ArrayList<Display.ColorTransform> colorTransforms = new ArrayList<>();
+
+            // Build an updated list of all existing color transforms.
+            boolean colorTransformsAdded = false;
+            Display.ColorTransform activeColorTransform = null;
             for (int i = 0; i < physicalDisplayInfos.length; i++) {
                 SurfaceControl.PhysicalDisplayInfo info = physicalDisplayInfos[i];
+                // First check to see if we've already added this color transform
+                boolean existingMode = false;
+                for (int j = 0; j < colorTransforms.size(); j++) {
+                    if (colorTransforms.get(j).getColorTransform() == info.colorTransform) {
+                        existingMode = true;
+                        break;
+                    }
+                }
+                if (existingMode) {
+                    continue;
+                }
+                Display.ColorTransform colorTransform = findColorTransform(info);
+                if (colorTransform == null) {
+                    colorTransform = createColorTransform(info.colorTransform);
+                    colorTransformsAdded = true;
+                }
+                colorTransforms.add(colorTransform);
+                if (i == activeDisplayInfo) {
+                    activeColorTransform = colorTransform;
+                }
+            }
+
+            // Build an updated list of all existing modes.
+            ArrayList<DisplayModeRecord> records = new ArrayList<DisplayModeRecord>();
+            boolean modesAdded = false;
+            for (int i = 0; i < physicalDisplayInfos.length; i++) {
+                SurfaceControl.PhysicalDisplayInfo info = physicalDisplayInfos[i];
+                // First, check to see if we've already added a matching mode. Since not all
+                // configuration options are exposed via Display.Mode, it's possible that we have
+                // multiple PhysicalDisplayInfos that would generate the same Display.Mode.
+                boolean existingMode = false;
+                for (int j = 0; j < records.size(); j++) {
+                    if (records.get(j).hasMatchingMode(info)) {
+                        existingMode = true;
+                        break;
+                    }
+                }
+                if (existingMode) {
+                    continue;
+                }
+                // If we haven't already added a mode for this configuration to the new set of
+                // supported modes then check to see if we have one in the prior set of supported
+                // modes to reuse.
                 DisplayModeRecord record = findDisplayModeRecord(info);
-                if (record != null) {
-                    record.mPhysIndex = i;
-                } else {
-                    record = new DisplayModeRecord(info, i);
+                if (record == null) {
+                    record = new DisplayModeRecord(info);
                     modesAdded = true;
                 }
                 records.add(record);
-                if (i == activeDisplayInfo) {
+            }
+
+            // Get the currently active mode
+            DisplayModeRecord activeRecord = null;
+            for (int i = 0; i < records.size(); i++) {
+                DisplayModeRecord record = records.get(i);
+                if (record.hasMatchingMode(physicalDisplayInfos[activeDisplayInfo])){
                     activeRecord = record;
+                    break;
                 }
             }
             // Check whether surface flinger spontaneously changed modes out from under us. Schedule
@@ -192,25 +253,48 @@
                 mActiveModeInvalid = true;
                 sendTraversalRequestLocked();
             }
-            // If no modes were added and we have the same number of modes as before, then nothing
-            // actually changed except possibly the physical index (which we only care about when
-            // setting the mode) so we're done.
-            if (records.size() == mSupportedModes.size() && !modesAdded) {
+            // Check whether surface flinger spontaneously changed color transforms out from under
+            // us.
+            if (mActiveColorTransformId != 0
+                    && mActiveColorTransformId != activeColorTransform.getId()) {
+                mActiveColorTransformInvalid = true;
+                sendTraversalRequestLocked();
+            }
+
+            boolean colorTransformsChanged =
+                    colorTransforms.size() != mSupportedColorTransforms.size()
+                    || colorTransformsAdded;
+            boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded;
+            // If neither the records nor the supported color transforms have changed then we're
+            // done here.
+            if (!recordsChanged && !colorTransformsChanged) {
                 return false;
             }
             // Update the index of modes.
             mHavePendingChanges = true;
+
             mSupportedModes.clear();
             for (DisplayModeRecord record : records) {
                 mSupportedModes.put(record.mMode.getModeId(), record);
             }
-            // Update the default mode if needed.
-            if (mSupportedModes.indexOfKey(mDefaultModeId) < 0) {
+            mSupportedColorTransforms.clear();
+            for (Display.ColorTransform colorTransform : colorTransforms) {
+                mSupportedColorTransforms.put(colorTransform.getId(), colorTransform);
+            }
+
+            // Update the default mode and color transform if needed. This needs to be done in
+            // tandem so we always have a default state to fall back to.
+            if (findDisplayInfoIndexLocked(mDefaultColorTransformId, mDefaultModeId) < 0) {
                 if (mDefaultModeId != 0) {
-                    Slog.w(TAG, "Default display mode no longer available, using currently active"
-                            + " mode as default.");
+                    Slog.w(TAG, "Default display mode no longer available, using currently"
+                            + " active mode as default.");
                 }
                 mDefaultModeId = activeRecord.mMode.getModeId();
+                if (mDefaultColorTransformId != 0) {
+                    Slog.w(TAG, "Default color transform no longer available, using currently"
+                            + " active color transform as default");
+                }
+                mDefaultColorTransformId = activeColorTransform.getId();
             }
             // Determine whether the active mode is still there.
             if (mSupportedModes.indexOfKey(mActiveModeId) < 0) {
@@ -221,6 +305,16 @@
                 mActiveModeId = mDefaultModeId;
                 mActiveModeInvalid = true;
             }
+
+            // Determine whether the active color transform is still there.
+            if (mSupportedColorTransforms.indexOfKey(mActiveColorTransformId) < 0) {
+                if (mActiveColorTransformId != 0) {
+                    Slog.w(TAG, "Active color transform no longer available, reverting"
+                            + " to default transform.");
+                }
+                mActiveColorTransformId = mDefaultColorTransformId;
+                mActiveColorTransformInvalid = true;
+            }
             // Schedule traversals so that we apply pending changes.
             sendTraversalRequestLocked();
             return true;
@@ -229,13 +323,23 @@
         private DisplayModeRecord findDisplayModeRecord(SurfaceControl.PhysicalDisplayInfo info) {
             for (int i = 0; i < mSupportedModes.size(); i++) {
                 DisplayModeRecord record = mSupportedModes.valueAt(i);
-                if (record.mPhys.equals(info)) {
+                if (record.hasMatchingMode(info)) {
                     return record;
                 }
             }
             return null;
         }
 
+        private Display.ColorTransform findColorTransform(SurfaceControl.PhysicalDisplayInfo info) {
+            for (int i = 0; i < mSupportedColorTransforms.size(); i++) {
+                Display.ColorTransform transform = mSupportedColorTransforms.valueAt(i);
+                if (transform.getColorTransform() == info.colorTransform) {
+                    return transform;
+                }
+            }
+            return null;
+        }
+
         @Override
         public void applyPendingDisplayDeviceInfoChangesLocked() {
             if (mHavePendingChanges) {
@@ -247,7 +351,7 @@
         @Override
         public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
             if (mInfo == null) {
-                SurfaceControl.PhysicalDisplayInfo phys = mSupportedModes.get(mActiveModeId).mPhys;
+                SurfaceControl.PhysicalDisplayInfo phys = mDisplayInfos[mActivePhysIndex];
                 mInfo = new DisplayDeviceInfo();
                 mInfo.width = phys.width;
                 mInfo.height = phys.height;
@@ -258,6 +362,13 @@
                     DisplayModeRecord record = mSupportedModes.valueAt(i);
                     mInfo.supportedModes[i] = record.mMode;
                 }
+                mInfo.colorTransformId = mActiveColorTransformId;
+                mInfo.defaultColorTransformId = mDefaultColorTransformId;
+                mInfo.supportedColorTransforms =
+                        new Display.ColorTransform[mSupportedColorTransforms.size()];
+                for (int i = 0; i < mSupportedColorTransforms.size(); i++) {
+                    mInfo.supportedColorTransforms[i] = mSupportedColorTransforms.valueAt(i);
+                }
                 mInfo.appVsyncOffsetNanos = phys.appVsyncOffsetNanos;
                 mInfo.presentationDeadlineNanos = phys.presentationDeadlineNanos;
                 mInfo.state = mState;
@@ -402,7 +513,8 @@
         }
 
         @Override
-        public void requestModeInTransactionLocked(int modeId) {
+        public void requestColorTransformAndModeInTransactionLocked(
+                int colorTransformId, int modeId) {
             if (modeId == 0) {
                 modeId = mDefaultModeId;
             } else if (mSupportedModes.indexOfKey(modeId) < 0) {
@@ -410,13 +522,37 @@
                         + " reverting to default display mode.");
                 modeId = mDefaultModeId;
             }
-            if (mActiveModeId == modeId && !mActiveModeInvalid) {
+
+            if (colorTransformId == 0) {
+                colorTransformId = mDefaultColorTransformId;
+            } else if (mSupportedColorTransforms.indexOfKey(colorTransformId) < 0) {
+                Slog.w(TAG, "Requested color transform " + colorTransformId + " is not supported"
+                        + " by this display, reverting to the default color transform");
+                colorTransformId = mDefaultColorTransformId;
+            }
+            int physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId);
+            if (physIndex < 0) {
+                Slog.w(TAG, "Requested color transform, mode ID pair (" + colorTransformId + ", "
+                        + modeId + ") not available, trying color transform with default mode ID");
+                modeId = mDefaultModeId;
+                physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId);
+                if (physIndex < 0) {
+                    Slog.w(TAG, "Requested color transform with default mode ID still not"
+                            + " available, falling back to default color transform with default"
+                            + " mode.");
+                    colorTransformId = mDefaultColorTransformId;
+                    physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId);
+                }
+            }
+            if (physIndex > 0 && mActivePhysIndex == physIndex) {
                 return;
             }
-            DisplayModeRecord record = mSupportedModes.get(modeId);
-            SurfaceControl.setActiveConfig(getDisplayTokenLocked(), record.mPhysIndex);
+            SurfaceControl.setActiveConfig(getDisplayTokenLocked(), physIndex);
+            mActivePhysIndex = physIndex;
             mActiveModeId = modeId;
             mActiveModeInvalid = false;
+            mActiveColorTransformId = colorTransformId;
+            mActiveColorTransformInvalid = false;
             updateDeviceInfoLocked();
         }
 
@@ -424,10 +560,43 @@
         public void dumpLocked(PrintWriter pw) {
             super.dumpLocked(pw);
             pw.println("mBuiltInDisplayId=" + mBuiltInDisplayId);
+            pw.println("mActivePhysIndex=" + mActivePhysIndex);
             pw.println("mActiveModeId=" + mActiveModeId);
+            pw.println("mActiveColorTransformId=" + mActiveColorTransformId);
             pw.println("mState=" + Display.stateToString(mState));
             pw.println("mBrightness=" + mBrightness);
             pw.println("mBacklight=" + mBacklight);
+            pw.println("mDisplayInfos=");
+            for (int i = 0; i < mDisplayInfos.length; i++) {
+                pw.println("  " + mDisplayInfos[i]);
+            }
+            pw.println("mSupportedModes=");
+            for (int i = 0; i < mSupportedModes.size(); i++) {
+                pw.println("  " + mSupportedModes.valueAt(i));
+            }
+            pw.println("mSupportedColorTransforms=[");
+            for (int i = 0; i < mSupportedColorTransforms.size(); i++) {
+                if (i != 0) {
+                    pw.print(", ");
+                }
+                pw.print(mSupportedColorTransforms.valueAt(i));
+            }
+            pw.println("]");
+        }
+
+        private int findDisplayInfoIndexLocked(int colorTransformId, int modeId) {
+            DisplayModeRecord record = mSupportedModes.get(modeId);
+            Display.ColorTransform transform = mSupportedColorTransforms.get(colorTransformId);
+            if (record != null && transform != null) {
+                for (int i = 0; i < mDisplayInfos.length; i++) {
+                    SurfaceControl.PhysicalDisplayInfo info = mDisplayInfos[i];
+                    if (info.colorTransform == transform.getColorTransform()
+                            && record.hasMatchingMode(info)){
+                        return i;
+                    }
+                }
+            }
+            return -1;
         }
 
         private void updateDeviceInfoLocked() {
@@ -441,13 +610,28 @@
      */
     private static final class DisplayModeRecord {
         public final Display.Mode mMode;
-        public final SurfaceControl.PhysicalDisplayInfo mPhys;
-        public int mPhysIndex;
 
-        public DisplayModeRecord(SurfaceControl.PhysicalDisplayInfo phys, int physIndex) {
+        public DisplayModeRecord(SurfaceControl.PhysicalDisplayInfo phys) {
             mMode = createMode(phys.width, phys.height, phys.refreshRate);
-            mPhys = phys;
-            mPhysIndex = physIndex;
+        }
+
+        /**
+         * Returns whether the mode generated by the given PhysicalDisplayInfo matches the mode
+         * contained by the record modulo mode ID.
+         *
+         * Note that this doesn't necessarily mean the the PhysicalDisplayInfos are identical, just
+         * that they generate identical modes.
+         */
+        public boolean hasMatchingMode(SurfaceControl.PhysicalDisplayInfo info) {
+            int modeRefreshRate = Float.floatToIntBits(mMode.getRefreshRate());
+            int displayInfoRefreshRate = Float.floatToIntBits(info.refreshRate);
+            return mMode.getPhysicalWidth() == info.width
+                    && mMode.getPhysicalHeight() == info.height
+                    && modeRefreshRate == displayInfoRefreshRate;
+        }
+
+        public String toString() {
+            return "DisplayModeRecord{mMode=" + mMode + "}";
         }
     }
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 6efc99a..6dae397 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -74,6 +74,7 @@
     private boolean mHasContent;
 
     private int mRequestedModeId;
+    private int mRequestedColorTransformId;
 
     // The display offsets to apply to the display projection.
     private int mDisplayOffsetX;
@@ -235,6 +236,11 @@
             mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId;
             mBaseDisplayInfo.supportedModes = Arrays.copyOf(
                     deviceInfo.supportedModes, deviceInfo.supportedModes.length);
+            mBaseDisplayInfo.colorTransformId = deviceInfo.colorTransformId;
+            mBaseDisplayInfo.defaultColorTransformId = deviceInfo.defaultColorTransformId;
+            mBaseDisplayInfo.supportedColorTransforms = Arrays.copyOf(
+                    deviceInfo.supportedColorTransforms,
+                    deviceInfo.supportedColorTransforms.length);
             mBaseDisplayInfo.logicalDensityDpi = deviceInfo.densityDpi;
             mBaseDisplayInfo.physicalXDpi = deviceInfo.xDpi;
             mBaseDisplayInfo.physicalYDpi = deviceInfo.yDpi;
@@ -275,11 +281,12 @@
         // Set the layer stack.
         device.setLayerStackInTransactionLocked(isBlanked ? BLANK_LAYER_STACK : mLayerStack);
 
-        // Set the mode.
+        // Set the color transform and mode.
         if (device == mPrimaryDisplayDevice) {
-            device.requestModeInTransactionLocked(mRequestedModeId);
+            device.requestColorTransformAndModeInTransactionLocked(
+                    mRequestedColorTransformId, mRequestedModeId);
         } else {
-            device.requestModeInTransactionLocked(0);  // Revert to default.
+            device.requestColorTransformAndModeInTransactionLocked(0, 0);  // Revert to default.
         }
 
         // Only grab the display info now as it may have been changed based on the requests above.
@@ -383,6 +390,18 @@
     }
 
     /**
+     * Requests the given color transform.
+     */
+    public void setRequestedColorTransformIdLocked(int colorTransformId) {
+        mRequestedColorTransformId = colorTransformId;
+    }
+
+    /** Returns the pending requested color transform. */
+    public int getRequestedColorTransformIdLocked() {
+        return mRequestedColorTransformId;
+    }
+
+    /**
      * Gets the burn-in offset in X.
      */
     public int getDisplayOffsetXLocked() {
@@ -409,6 +428,7 @@
         pw.println("mLayerStack=" + mLayerStack);
         pw.println("mHasContent=" + mHasContent);
         pw.println("mRequestedMode=" + mRequestedModeId);
+        pw.println("mRequestedColorTransformId=" + mRequestedColorTransformId);
         pw.println("mDisplayOffset=(" + mDisplayOffsetX + ", " + mDisplayOffsetY + ")");
         pw.println("mPrimaryDisplayDevice=" + (mPrimaryDisplayDevice != null ?
                 mPrimaryDisplayDevice.getNameLocked() : "null"));
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 0bddff0..cf6264a 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -310,7 +310,7 @@
         }
 
         @Override
-        public void requestModeInTransactionLocked(int id) {
+        public void requestColorTransformAndModeInTransactionLocked(int color, int id) {
             int index = -1;
             if (id == 0) {
                 // Use the default.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b7a587b..ac809f9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -12380,6 +12380,7 @@
 
         // Retrieve PackageSettings and parse package
         final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
+                | PackageParser.PARSE_ENFORCE_CODE
                 | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
                 | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0);
         PackageParser pp = new PackageParser();
@@ -15901,14 +15902,28 @@
         }
     }
 
-    private void loadPrivatePackages(VolumeInfo vol) {
+    private void loadPrivatePackages(final VolumeInfo vol) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                loadPrivatePackagesInner(vol);
+            }
+        });
+    }
+
+    private void loadPrivatePackagesInner(VolumeInfo vol) {
         final ArrayList<ApplicationInfo> loaded = new ArrayList<>();
         final int parseFlags = mDefParseFlags | PackageParser.PARSE_EXTERNAL_STORAGE;
-        synchronized (mInstallLock) {
+
+        final VersionInfo ver;
+        final List<PackageSetting> packages;
         synchronized (mPackages) {
-            final VersionInfo ver = mSettings.findOrCreateVersion(vol.fsUuid);
-            final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(vol.fsUuid);
-            for (PackageSetting ps : packages) {
+            ver = mSettings.findOrCreateVersion(vol.fsUuid);
+            packages = mSettings.getVolumePackagesLPr(vol.fsUuid);
+        }
+
+        for (PackageSetting ps : packages) {
+            synchronized (mInstallLock) {
                 final PackageParser.Package pkg;
                 try {
                     pkg = scanPackageTracedLI(ps.codePath, parseFlags, SCAN_INITIAL, 0L, null);
@@ -15921,7 +15936,9 @@
                     deleteCodeCacheDirsLI(ps.volumeUuid, ps.name);
                 }
             }
+        }
 
+        synchronized (mPackages) {
             int updateFlags = UPDATE_PERMISSIONS_ALL;
             if (ver.sdkVersion != mSdkVersion) {
                 logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to "
@@ -15935,13 +15952,21 @@
 
             mSettings.writeLPr();
         }
-        }
 
         if (DEBUG_INSTALL) Slog.d(TAG, "Loaded packages " + loaded);
         sendResourcesChangedBroadcast(true, false, loaded, null);
     }
 
-    private void unloadPrivatePackages(VolumeInfo vol) {
+    private void unloadPrivatePackages(final VolumeInfo vol) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                unloadPrivatePackagesInner(vol);
+            }
+        });
+    }
+
+    private void unloadPrivatePackagesInner(VolumeInfo vol) {
         final ArrayList<ApplicationInfo> unloaded = new ArrayList<>();
         synchronized (mInstallLock) {
         synchronized (mPackages) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ff58757..04d382d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1345,7 +1345,6 @@
         mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-        mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
 
         // Init display burn-in protection
         boolean burnInProtectionEnabled = context.getResources().getBoolean(
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index cc9efdb..58f0448 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -72,6 +72,12 @@
     // For handling display rotations.
     private Rect mTmpRect2 = new Rect();
 
+    // Whether the task is currently being drag-resized
+    private boolean mDragResizing;
+
+    // Whether the task is starting or ending to be drag-resized
+    private boolean mDragResizeChanging;
+
     // The particular window with FLAG_DIM_BEHIND set. If null, hide mDimLayer.
     WindowStateAnimator mDimWinAnimator;
     // Used to support {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
@@ -227,10 +233,34 @@
         return boundsChange;
     }
 
+    boolean resizeLocked(Rect bounds, Configuration configuration) {
+        int boundsChanged = setBounds(bounds, configuration);
+        if (mDragResizeChanging) {
+            boundsChanged |= BOUNDS_CHANGE_SIZE;
+            mDragResizeChanging = false;
+        }
+        if (boundsChanged == BOUNDS_CHANGE_NONE) {
+            return false;
+        }
+        if ((boundsChanged & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) {
+            resizeWindows();
+        }
+        return true;
+    }
+
     void getBounds(Rect out) {
         out.set(mBounds);
     }
 
+    void setDragResizing(boolean dragResizing) {
+        mDragResizeChanging = mDragResizing != dragResizing;
+        mDragResizing = dragResizing;
+    }
+
+    boolean isDragResizing() {
+        return mDragResizing;
+    }
+
     void updateDisplayInfo(final DisplayContent displayContent) {
         if (displayContent == null) {
             return;
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 17b56ba..5487349 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -86,8 +86,7 @@
     private int mMinVisibleWidth;
     private int mMinVisibleHeight;
 
-    private int mTaskId;
-    private TaskStack mStack;
+    private Task mTask;
     private boolean mResizing;
     private final Rect mWindowOriginalBounds = new Rect();
     private final Rect mWindowDragBounds = new Rect();
@@ -136,7 +135,7 @@
                         }
                         try {
                             mService.mActivityManager.resizeTask(
-                                    mTaskId, mWindowDragBounds, true /* resizedByUser */);
+                                    mTask.mTaskId, mWindowDragBounds, true /* resizedByUser */);
                         } catch(RemoteException e) {}
                     } break;
 
@@ -156,21 +155,29 @@
                 }
 
                 if (endDrag) {
-                    mResizing = false;
+                    synchronized (mService.mWindowMap) {
+                        endDragLocked();
+                    }
                     try {
-                        mService.mActivityManager.resizeTask(
-                                mTaskId, mWindowDragBounds, true /* resizedByUser */);
+                        if (mResizing) {
+                            // We were using fullscreen surface during resizing. Request
+                            // resizeTask() one last time to restore surface to window size.
+                            mService.mActivityManager.resizeTask(
+                                    mTask.mTaskId, mWindowDragBounds, true /* resizedByUser */);
+                        }
+
+                        if (mCurrentDimSide != CTRL_NONE) {
+                            final int createMode = mCurrentDimSide == CTRL_LEFT
+                                    ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
+                                    : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+                            mService.mActivityManager.moveTaskToDockedStack(
+                                    mTask.mTaskId, createMode, true /*toTop*/);
+                        }
                     } catch(RemoteException e) {}
+
                     // Post back to WM to handle clean-ups. We still need the input
                     // event handler for the last finishInputEvent()!
                     mService.mH.sendEmptyMessage(H.FINISH_TASK_POSITIONING);
-                    if (mCurrentDimSide != CTRL_NONE) {
-                        final int createMode = mCurrentDimSide == CTRL_LEFT
-                                ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
-                                : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-                        mService.mActivityManager.moveTaskToDockedStack(
-                                mTaskId, createMode, true /*toTop*/);
-                    }
                 }
                 handled = true;
             } catch (Exception e) {
@@ -291,10 +298,6 @@
         mService.resumeRotationLocked();
     }
 
-    boolean isTaskResizing(final Task task) {
-        return mResizing && task != null && mTaskId == task.mTaskId;
-    }
-
     void startDragLocked(WindowState win, boolean resize, float startX, float startY) {
         if (DEBUG_TASK_POSITIONING) {
             Slog.d(TAG, "startDragLocked: win=" + win + ", resize=" + resize
@@ -318,13 +321,16 @@
             mResizing = true;
         }
 
-        final Task task = win.getTask();
-        mTaskId = task.mTaskId;
-        mStack = task.mStack;
+        mTask = win.getTask();
         mStartDragX = startX;
         mStartDragY = startY;
 
-        mService.getTaskBounds(mTaskId, mWindowOriginalBounds);
+        mService.getTaskBounds(mTask.mTaskId, mWindowOriginalBounds);
+    }
+
+    private void endDragLocked() {
+        mResizing = false;
+        mTask.setDragResizing(false);
     }
 
     /** Returns true if the move operation should be ended. */
@@ -354,11 +360,12 @@
                 bottom = Math.max(top + mMinVisibleHeight, bottom + deltaY);
             }
             mWindowDragBounds.set(left, top, right, bottom);
+            mTask.setDragResizing(true);
             return false;
         }
 
         // This is a moving operation.
-        mStack.getBounds(mTmpRect);
+        mTask.mStack.getBounds(mTmpRect);
         mTmpRect.inset(mMinVisibleWidth, mMinVisibleHeight);
         if (!mTmpRect.contains((int) x, (int) y)) {
             // We end the moving operation if position is outside the stack bounds.
@@ -397,13 +404,13 @@
      * shouldn't be shown.
      */
     private int getDimSide(int x) {
-        if (mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
-                || !mStack.isFullscreen()
+        if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
+                || !mTask.mStack.isFullscreen()
                 || mService.mCurConfiguration.orientation != ORIENTATION_LANDSCAPE) {
             return CTRL_NONE;
         }
 
-        mStack.getBounds(mTmpRect);
+        mTask.mStack.getBounds(mTmpRect);
         if (x - mSideMargin <= mTmpRect.left) {
             return CTRL_LEFT;
         }
@@ -415,7 +422,7 @@
     }
 
     private void showDimLayer() {
-        mStack.getBounds(mTmpRect);
+        mTask.mStack.getBounds(mTmpRect);
         if (mCurrentDimSide == CTRL_LEFT) {
             mTmpRect.right = mTmpRect.centerX();
         } else if (mCurrentDimSide == CTRL_RIGHT) {
@@ -433,7 +440,7 @@
 
     @Override /** {@link DimLayer.DimLayerUser} */
     public DisplayInfo getDisplayInfo() {
-        return mStack.getDisplayInfo();
+        return mTask.mStack.getDisplayInfo();
     }
 
     private int getDragLayerLocked() {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 1e6fab6..571540f 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -20,6 +20,7 @@
 import static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerService.TAG;
 
+import android.annotation.IntDef;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Debug;
@@ -32,6 +33,8 @@
 import com.android.server.EventLogTags;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
 public class TaskStack implements DimLayer.DimLayerUser {
@@ -73,6 +76,20 @@
     /** Detach this stack from its display when animation completes. */
     boolean mDeferDetach;
 
+    static final int DOCKED_INVALID = -1;
+    static final int DOCKED_LEFT = 1;
+    static final int DOCKED_TOP = 2;
+    static final int DOCKED_RIGHT = 3;
+    static final int DOCKED_BOTTOM = 4;
+    @IntDef({
+            DOCKED_INVALID,
+            DOCKED_LEFT,
+            DOCKED_TOP,
+            DOCKED_RIGHT,
+            DOCKED_BOTTOM})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface DockSide {}
+
     TaskStack(WindowManagerService service, int stackId) {
         mService = service;
         mStackId = stackId;
@@ -508,4 +525,36 @@
     public String toString() {
         return "{stackId=" + mStackId + " tasks=" + mTasks + "}";
     }
+
+    /**
+     * For docked workspace provides information which side of the screen was the dock anchored.
+     */
+    @DockSide
+    int getDockSide() {
+        if (mStackId != DOCKED_STACK_ID) {
+            return DOCKED_INVALID;
+        }
+        if (mDisplayContent == null) {
+            return DOCKED_INVALID;
+        }
+        mDisplayContent.getLogicalDisplayRect(mTmpRect);
+        final int orientation = mService.mCurConfiguration.orientation;
+        if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+            // Portrait mode, docked either at the top or the bottom.
+            if (mTmpRect.top - mBounds.top < mTmpRect.bottom - mBounds.bottom) {
+                return DOCKED_TOP;
+            } else {
+                return DOCKED_BOTTOM;
+            }
+        } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            // Landscape mode, docked either on the left or on the right.
+            if (mTmpRect.left - mBounds.left < mTmpRect.right - mBounds.right) {
+                return DOCKED_LEFT;
+            } else {
+                return DOCKED_RIGHT;
+            }
+        } else {
+            return DOCKED_INVALID;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5bf296e..4e4e57b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2589,9 +2589,13 @@
                     }
                 }
 
-                dragResizing = win.isDragResizing();
-                if (win.mDragResizing != dragResizing) {
-                    win.mDragResizing = dragResizing;
+                // If we're starting a drag-resize, we'll be changing the surface size as well as
+                // notifying the client to render to with an offset from the surface's top-left.
+                // Do a screen freeze, and keep the old surface until the the first frame drawn to
+                // the new surface comes back, so that we avoid a flash due to mismatching surface
+                // setups on window manager side and client side.
+                if (win.isDragResizeChanged()) {
+                    win.setDragResizing();
                     if (win.mHasSurface) {
                         winAnimator.mDestroyPendingSurfaceUponRedraw = true;
                         winAnimator.mSurfaceDestroyDeferred = true;
@@ -2600,6 +2604,7 @@
                         toBeDisplayed = true;
                     }
                 }
+                dragResizing = win.isDragResizing();
                 try {
                     if (!win.mHasSurface) {
                         surfaceChanged = true;
@@ -3670,12 +3675,8 @@
                     // pretend like we didn't see that.
                     return;
                 }
-                final boolean windowIsTranslucentDefined = ent.array.hasValue(
-                        com.android.internal.R.styleable.Window_windowIsTranslucent);
                 final boolean windowIsTranslucent = ent.array.getBoolean(
                         com.android.internal.R.styleable.Window_windowIsTranslucent, false);
-                final boolean windowSwipeToDismiss = ent.array.getBoolean(
-                        com.android.internal.R.styleable.Window_windowSwipeToDismiss, false);
                 final boolean windowIsFloating = ent.array.getBoolean(
                         com.android.internal.R.styleable.Window_windowIsFloating, false);
                 final boolean windowShowWallpaper = ent.array.getBoolean(
@@ -3685,7 +3686,7 @@
                 if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Translucent=" + windowIsTranslucent
                         + " Floating=" + windowIsFloating
                         + " ShowWallpaper=" + windowShowWallpaper);
-                if (windowIsTranslucent || (!windowIsTranslucentDefined && windowSwipeToDismiss)) {
+                if (windowIsTranslucent) {
                     return;
                 }
                 if (windowIsFloating || windowDisableStarting) {
@@ -4688,16 +4689,10 @@
                 throw new IllegalArgumentException("resizeTask: taskId " + taskId
                         + " not found.");
             }
-            final int boundsChanged = task.setBounds(bounds, configuration);
-            if (boundsChanged != Task.BOUNDS_CHANGE_NONE) {
-                if ((boundsChanged & Task.BOUNDS_CHANGE_SIZE) == Task.BOUNDS_CHANGE_SIZE) {
-                    task.resizeWindows();
-                }
 
-                if (relayout) {
-                    task.getDisplayContent().layoutNeeded = true;
-                    mWindowPlacerLocked.performSurfacePlacement();
-                }
+            if (task.resizeLocked(bounds, configuration) && relayout) {
+                task.getDisplayContent().layoutNeeded = true;
+                mWindowPlacerLocked.performSurfacePlacement();
             }
         }
     }
@@ -8480,7 +8475,7 @@
                 Slog.v(TAG, "Win " + w + " config changed: "
                         + mCurConfiguration);
             }
-            final boolean dragResizingChanged = w.mDragResizing != w.isDragResizing();
+            final boolean dragResizingChanged = w.isDragResizeChanged();
             if (localLOGV) Slog.v(TAG, "Resizing " + w
                     + ": configChanged=" + configChanged
                     + " dragResizingChanged=" + dragResizingChanged
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index fa28eba..a6478a0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
@@ -129,6 +128,7 @@
     boolean mAttachedHidden;    // is our parent window hidden?
     boolean mWallpaperVisible;  // for wallpaper, what was last vis report?
     boolean mDragResizing;
+    boolean mDragResizeChanging;
 
     RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
 
@@ -382,6 +382,8 @@
      */
     PowerManager.WakeLock mDrawLock;
 
+    final private Rect mTmpRect = new Rect();
+
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
            int viewVisibility, final DisplayContent displayContent) {
@@ -937,24 +939,33 @@
      *        bounds will be returned.
      */
     void getVisibleBounds(Rect bounds, boolean forTouch) {
-        final boolean useStackBounds = mAppToken != null && mAppToken.mCropWindowsToStack;
+        boolean intersectWithStackBounds = mAppToken != null && mAppToken.mCropWindowsToStack;
         boolean isFreeform = false;
         bounds.setEmpty();
-        if (useStackBounds) {
+        mTmpRect.setEmpty();
+        if (intersectWithStackBounds) {
             final TaskStack stack = getStack();
             if (stack != null) {
-                stack.getBounds(bounds);
-                isFreeform = stack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
-            }
-        } else {
-            final Task task = getTask();
-            if (task != null) {
-                task.getBounds(bounds);
-                isFreeform = task.inFreeformWorkspace();
+                stack.getBounds(mTmpRect);
+            } else {
+                intersectWithStackBounds = false;
             }
         }
+
+        final Task task = getTask();
+        if (task != null) {
+            task.getBounds(bounds);
+            isFreeform = task.inFreeformWorkspace();
+            if (intersectWithStackBounds) {
+                bounds.intersect(mTmpRect);
+            }
+        }
+
         if (bounds.isEmpty()) {
             bounds.set(mFrame);
+            if (intersectWithStackBounds) {
+                bounds.intersect(mTmpRect);
+            }
             return;
         }
         if (forTouch && isFreeform) {
@@ -1691,9 +1702,18 @@
         return task != null && task.inFreeformWorkspace();
     }
 
-    boolean isDragResizing() {
+    boolean isDragResizeChanged() {
         final Task task = getTask();
-        return mService.mTaskPositioner != null && mService.mTaskPositioner.isTaskResizing(task);
+        return task != null && mDragResizing != task.isDragResizing();
+    }
+
+    void setDragResizing() {
+        final Task task = getTask();
+        mDragResizing = task != null && task.isDragResizing();
+    }
+
+    boolean isDragResizing() {
+        return mDragResizing;
     }
 
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index e6fef2f..07e1fce 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -818,7 +818,7 @@
                 // so that we don't need to reallocate during the process. This also prevents
                 // buffer drops due to size mismatch.
                 final DisplayInfo displayInfo = w.getDisplayInfo();
-                if (displayInfo != null && w.mDragResizing) {
+                if (displayInfo != null && w.isDragResizing()) {
                     left = 0;
                     top = 0;
                     width = displayInfo.logicalWidth;
@@ -1211,6 +1211,13 @@
             return;
         } else if (mIsWallpaper && mService.mWindowPlacerLocked.mWallpaperActionPending) {
             return;
+        } else if (mWin.isDragResizeChanged()) {
+            // This window is awaiting a relayout because user just started (or ended)
+            // drag-resizing. The shown frame (which affects surface size and pos)
+            // should not be updated until we get next finished draw with the new surface.
+            // Otherwise one or two frames rendered with old settings would be displayed
+            // with new geometry.
+            return;
         }
 
         if (WindowManagerService.localLOGV) Slog.v(
@@ -1333,7 +1340,7 @@
 
         final boolean fullscreen = w.isFullscreen(displayInfo.appWidth, displayInfo.appHeight);
         final Rect clipRect = mTmpClipRect;
-        if (w.mDragResizing) {
+        if (w.isDragResizing()) {
             // When we're doing a drag-resizing, the surface is set up to cover full screen.
             // Set the clip rect to be the same size so that we don't get any scaling.
             clipRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
@@ -1423,7 +1430,7 @@
             // so that we don't need to reallocate during the process. This also prevents
             // buffer drops due to size mismatch.
             final DisplayInfo displayInfo = w.getDisplayInfo();
-            if (displayInfo != null && w.mDragResizing) {
+            if (displayInfo != null && w.isDragResizing()) {
                 left = 0;
                 top = 0;
                 width = displayInfo.logicalWidth;
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 0c3f9da..7f813ec 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -419,10 +419,9 @@
         private boolean setUsbConfig(String config) {
             if (DEBUG) Slog.d(TAG, "setUsbConfig(" + config + ")");
             // set the new configuration
-            String oldConfig = SystemProperties.get(USB_CONFIG_PROPERTY);
-            if (!config.equals(oldConfig)) {
-                SystemProperties.set(USB_CONFIG_PROPERTY, config);
-            }
+            // we always set it due to b/23631400, where adbd was getting killed
+            // and not restarted due to property timeouts on some devices
+            SystemProperties.set(USB_CONFIG_PROPERTY, config);
             return waitForState(config);
         }
 
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 10cf5c1..b028ce6 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -944,5 +944,14 @@
             </intent-filter>
         </activity>
 
+        <activity
+                android:name="MultiProducerActivity"
+                android:label="Threads/Multiple Producers">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java
new file mode 100644
index 0000000..b458d9b
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2014 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.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.DisplayListCanvas;
+import android.view.HardwareRenderer;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AbsoluteLayout;
+import android.widget.AbsoluteLayout.LayoutParams;
+
+public class MultiProducerActivity extends Activity implements OnClickListener {
+    private static final int DURATION = 800;
+    private View mBackgroundTarget = null;
+    private View mFrameTarget = null;
+    private View mContent = null;
+    // The width & height of our "output drawing".
+    private final int WIDTH = 900;
+    private final int HEIGHT = 600;
+    // A border width around the drawing.
+    private static final int BORDER_WIDTH = 20;
+    // The Gap between the content and the frame which should get filled on the right and bottom
+    // side by the backdrop.
+    final int CONTENT_GAP = 100;
+
+    // For debug purposes - disable drawing of frame / background.
+    private final boolean USE_FRAME = true;
+    private final boolean USE_BACK = true;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // To make things simple - we do a quick and dirty absolute layout.
+        final AbsoluteLayout layout = new AbsoluteLayout(this);
+
+        // Create the outer frame
+        if (USE_FRAME) {
+            mFrameTarget = new View(this);
+            LayoutParams frameLP = new LayoutParams(WIDTH, HEIGHT, 0, 0);
+            layout.addView(mFrameTarget, frameLP);
+        }
+
+        // Create the background which fills the gap between content and frame.
+        if (USE_BACK) {
+            mBackgroundTarget = new View(this);
+            LayoutParams backgroundLP = new LayoutParams(
+                    WIDTH - 2 * BORDER_WIDTH, HEIGHT - 2 * BORDER_WIDTH,
+                    BORDER_WIDTH, BORDER_WIDTH);
+            layout.addView(mBackgroundTarget, backgroundLP);
+        }
+
+        // Create the content
+        // Note: We reduce the size by CONTENT_GAP pixels on right and bottom, so that they get
+        // drawn by the backdrop.
+        mContent = new View(this);
+        mContent.setBackground(new ColorPulse(0xFFF44336, 0xFF9C27B0, null));
+        mContent.setOnClickListener(this);
+        LayoutParams contentLP = new LayoutParams(WIDTH - 2 * BORDER_WIDTH - CONTENT_GAP,
+                HEIGHT - 2 * BORDER_WIDTH - CONTENT_GAP, BORDER_WIDTH, BORDER_WIDTH);
+        layout.addView(mContent, contentLP);
+
+        setContentView(layout);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
+        if (view != null) {
+            view.post(mSetup);
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
+        if (view != null) {
+            view.removeCallbacks(mSetup);
+        }
+        if (mBgRenderer != null) {
+            mBgRenderer.destroy();
+            mBgRenderer = null;
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        sBlockThread.run();
+    }
+
+    private Runnable mSetup = new Runnable() {
+        @Override
+        public void run() {
+            View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
+            if (view == null) {
+                view.postDelayed(mSetup, 50);
+            }
+            HardwareRenderer renderer = view.getHardwareRenderer();
+            if (renderer == null || view.getWidth() == 0) {
+                view.postDelayed(mSetup, 50);
+            }
+            ThreadedRenderer threaded = (ThreadedRenderer) renderer;
+
+            mBgRenderer = new FakeFrame(threaded,mFrameTarget, mBackgroundTarget);
+            mBgRenderer.start();
+        }
+    };
+
+    private FakeFrame mBgRenderer;
+    private class FakeFrame extends Thread {
+        ThreadedRenderer mRenderer;
+        volatile boolean mRunning = true;
+        View mTargetFrame;
+        View mTargetBack;
+        Drawable mFrameContent;
+        Drawable mBackContent;
+        // The Z value where to place this.
+        int mZFrame;
+        int mZBack;
+        String mRenderNodeName;
+
+        FakeFrame(ThreadedRenderer renderer, View targetFrame, View targetBack) {
+            mRenderer = renderer;
+            mTargetFrame = targetFrame;
+
+            mTargetBack = targetBack;
+            mFrameContent = new ColorPulse(0xFF101010, 0xFF707070, new Rect(0, 0, WIDTH, HEIGHT));
+            mBackContent = new ColorPulse(0xFF909090, 0xFFe0e0e0, null);
+        }
+
+        @Override
+        public void run() {
+            Rect currentFrameBounds = new Rect();
+            Rect currentBackBounds = new Rect();
+            Rect newBounds = new Rect();
+            int[] surfaceOrigin = new int[2];
+            RenderNode nodeFrame = null;
+            RenderNode nodeBack = null;
+
+            // Since we are overriding the window painting logic we need to at least fill the
+            // surface with some window content (otherwise the world will go black).
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException e) {
+            }
+
+            if (mTargetBack != null) {
+                nodeBack = RenderNode.create("FakeBackdrop", null);
+                nodeBack.setClipToBounds(true);
+                mRenderer.addRenderNode(nodeBack, true);
+            }
+
+            if (mTargetFrame != null) {
+                nodeFrame = RenderNode.create("FakeFrame", null);
+                nodeFrame.setClipToBounds(true);
+                mRenderer.addRenderNode(nodeFrame, false);
+            }
+
+            while (mRunning) {
+                // Get the surface position to draw to within our surface.
+                surfaceOrigin[0] = 0;
+                surfaceOrigin[1] = 0;
+                // This call should be done while the rendernode's displaylist is produced.
+                // For simplicity of this test we do this before we kick off the draw.
+                mContent.getLocationInSurface(surfaceOrigin);
+                mRenderer.setContentOverdrawProtectionBounds(surfaceOrigin[0], surfaceOrigin[1],
+                        surfaceOrigin[0] + mContent.getWidth(),
+                        surfaceOrigin[1] + mContent.getHeight());
+                // Determine new position for frame.
+                if (nodeFrame != null) {
+                    surfaceOrigin[0] = 0;
+                    surfaceOrigin[1] = 0;
+                    mTargetFrame.getLocationInSurface(surfaceOrigin);
+                    newBounds.set(surfaceOrigin[0], surfaceOrigin[1],
+                            surfaceOrigin[0] + mTargetFrame.getWidth(),
+                            surfaceOrigin[1] + mTargetFrame.getHeight());
+                    if (!currentFrameBounds.equals(newBounds)) {
+                        currentFrameBounds.set(newBounds);
+                        nodeFrame.setLeftTopRightBottom(currentFrameBounds.left,
+                                currentFrameBounds.top,
+                                currentFrameBounds.right, currentFrameBounds.bottom);
+                    }
+
+                    // Draw frame
+                    DisplayListCanvas canvas = nodeFrame.start(currentFrameBounds.width(),
+                            currentFrameBounds.height());
+                    mFrameContent.draw(canvas);
+                    nodeFrame.end(canvas);
+                }
+
+                // Determine new position for backdrop
+                if (nodeBack != null) {
+                    surfaceOrigin[0] = 0;
+                    surfaceOrigin[1] = 0;
+                    mTargetBack.getLocationInSurface(surfaceOrigin);
+                    newBounds.set(surfaceOrigin[0], surfaceOrigin[1],
+                            surfaceOrigin[0] + mTargetBack.getWidth(),
+                            surfaceOrigin[1] + mTargetBack.getHeight());
+                    if (!currentBackBounds.equals(newBounds)) {
+                        currentBackBounds.set(newBounds);
+                        nodeBack.setLeftTopRightBottom(currentBackBounds.left,
+                                currentBackBounds.top,
+                                currentBackBounds.right, currentBackBounds.bottom);
+                    }
+
+                    // Draw Backdrop
+                    DisplayListCanvas canvas = nodeBack.start(currentBackBounds.width(),
+                            currentBackBounds.height());
+                    mBackContent.draw(canvas);
+                    nodeBack.end(canvas);
+                }
+
+                // we need to only render one guy - the rest will happen automatically (I think).
+                if (nodeFrame != null) {
+                    mRenderer.drawRenderNode(nodeFrame);
+                }
+                if (nodeBack != null) {
+                    mRenderer.drawRenderNode(nodeBack);
+                }
+                try {
+                    Thread.sleep(5);
+                } catch (InterruptedException e) {}
+            }
+            if (nodeFrame != null) {
+                mRenderer.removeRenderNode(nodeFrame);
+            }
+            if (nodeBack != null) {
+                mRenderer.removeRenderNode(nodeBack);
+            }
+        }
+
+        public void destroy() {
+            mRunning = false;
+            try {
+                join();
+            } catch (InterruptedException e) {}
+        }
+    }
+
+    private final static Runnable sBlockThread = new Runnable() {
+        @Override
+        public void run() {
+            try {
+                Thread.sleep(DURATION);
+            } catch (InterruptedException e) {
+            }
+        }
+    };
+
+    static class ColorPulse extends Drawable {
+
+        private int mColorStart;
+        private int mColorEnd;
+        private int mStep;
+        private Rect mRect;
+        private Paint mPaint = new Paint();
+
+        public ColorPulse(int color1, int color2, Rect rect) {
+            mColorStart = color1;
+            mColorEnd = color2;
+            if (rect != null) {
+                mRect = new Rect(rect.left + BORDER_WIDTH / 2, rect.top + BORDER_WIDTH / 2,
+                                 rect.right - BORDER_WIDTH / 2, rect.bottom - BORDER_WIDTH / 2);
+            }
+        }
+
+        static int evaluate(float fraction, int startInt, int endInt) {
+            int startA = (startInt >> 24) & 0xff;
+            int startR = (startInt >> 16) & 0xff;
+            int startG = (startInt >> 8) & 0xff;
+            int startB = startInt & 0xff;
+
+            int endA = (endInt >> 24) & 0xff;
+            int endR = (endInt >> 16) & 0xff;
+            int endG = (endInt >> 8) & 0xff;
+            int endB = endInt & 0xff;
+
+            return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
+                    (int)((startR + (int)(fraction * (endR - startR))) << 16) |
+                    (int)((startG + (int)(fraction * (endG - startG))) << 8) |
+                    (int)((startB + (int)(fraction * (endB - startB))));
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            float frac = mStep / 50.0f;
+            int color = evaluate(frac, mColorStart, mColorEnd);
+            if (mRect != null && !mRect.isEmpty()) {
+                mPaint.setStyle(Paint.Style.STROKE);
+                mPaint.setStrokeWidth(BORDER_WIDTH);
+                mPaint.setColor(color);
+                canvas.drawRect(mRect, mPaint);
+            } else {
+                canvas.drawColor(color);
+            }
+
+            mStep++;
+            if (mStep >= 50) {
+                mStep = 0;
+                int tmp = mColorStart;
+                mColorStart = mColorEnd;
+                mColorEnd = tmp;
+            }
+            invalidateSelf();
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+        }
+
+        @Override
+        public void setColorFilter(ColorFilter colorFilter) {
+        }
+
+        @Override
+        public int getOpacity() {
+            return mRect == null || mRect.isEmpty() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
+        }
+
+    }
+}
+