Merge "Re-enable wifi firmware debugging"
diff --git a/api/current.txt b/api/current.txt
index c161242..96ecad6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -499,6 +499,7 @@
     field public static final int ellipsize = 16842923; // 0x10100ab
     field public static final int ems = 16843096; // 0x1010158
     field public static final int enabled = 16842766; // 0x101000e
+    field public static final int encryptionAware = 16844038; // 0x1010506
     field public static final int end = 16843996; // 0x10104dc
     field public static final int endColor = 16843166; // 0x101019e
     field public static final deprecated int endYear = 16843133; // 0x101017d
@@ -559,6 +560,7 @@
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
+    field public static final int forceDeviceEncrypted = 16844037; // 0x1010505
     field public static final int foreground = 16843017; // 0x1010109
     field public static final int foregroundGravity = 16843264; // 0x1010200
     field public static final int foregroundTint = 16843885; // 0x101046d
@@ -931,6 +933,7 @@
     field public static final int port = 16842793; // 0x1010029
     field public static final int positiveButtonText = 16843253; // 0x10101f5
     field public static final int preferenceCategoryStyle = 16842892; // 0x101008c
+    field public static final int preferenceFragmentStyle = 16844039; // 0x1010507
     field public static final int preferenceInformationStyle = 16842893; // 0x101008d
     field public static final int preferenceLayoutChild = 16842900; // 0x1010094
     field public static final int preferenceScreenStyle = 16842891; // 0x101008b
@@ -1708,11 +1711,13 @@
     field public static final int icon = 16908294; // 0x1020006
     field public static final int icon1 = 16908295; // 0x1020007
     field public static final int icon2 = 16908296; // 0x1020008
+    field public static final int icon_frame = 16908350; // 0x102003e
     field public static final int input = 16908297; // 0x1020009
     field public static final int inputArea = 16908318; // 0x102001e
     field public static final int inputExtractEditText = 16908325; // 0x1020025
     field public static final int keyboardView = 16908326; // 0x1020026
     field public static final int list = 16908298; // 0x102000a
+    field public static final int list_container = 16908351; // 0x102003f
     field public static final int mask = 16908334; // 0x102002e
     field public static final int message = 16908299; // 0x102000b
     field public static final int navigationBarBackground = 16908336; // 0x1020030
@@ -1732,6 +1737,7 @@
     field public static final int stopSelectingText = 16908329; // 0x1020029
     field public static final int summary = 16908304; // 0x1020010
     field public static final int switchInputMethod = 16908324; // 0x1020024
+    field public static final int switch_widget = 16908352; // 0x1020040
     field public static final int tabcontent = 16908305; // 0x1020011
     field public static final int tabhost = 16908306; // 0x1020012
     field public static final int tabs = 16908307; // 0x1020013
@@ -5705,6 +5711,7 @@
     method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
     method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
     method public int getCurrentFailedPasswordAttempts();
+    method public java.lang.String getDeviceOwnerLockScreenInfo();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
     method public int getKeyguardDisabledFeatures(android.content.ComponentName);
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
@@ -5757,6 +5764,7 @@
     method public void setCameraDisabled(android.content.ComponentName, boolean);
     method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
     method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
+    method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
     method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 8729d84..0605851 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -591,6 +591,7 @@
     field public static final int ellipsize = 16842923; // 0x10100ab
     field public static final int ems = 16843096; // 0x1010158
     field public static final int enabled = 16842766; // 0x101000e
+    field public static final int encryptionAware = 16844038; // 0x1010506
     field public static final int end = 16843996; // 0x10104dc
     field public static final int endColor = 16843166; // 0x101019e
     field public static final deprecated int endYear = 16843133; // 0x101017d
@@ -651,6 +652,7 @@
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
+    field public static final int forceDeviceEncrypted = 16844037; // 0x1010505
     field public static final int foreground = 16843017; // 0x1010109
     field public static final int foregroundGravity = 16843264; // 0x1010200
     field public static final int foregroundTint = 16843885; // 0x101046d
@@ -1023,6 +1025,7 @@
     field public static final int port = 16842793; // 0x1010029
     field public static final int positiveButtonText = 16843253; // 0x10101f5
     field public static final int preferenceCategoryStyle = 16842892; // 0x101008c
+    field public static final int preferenceFragmentStyle = 16844039; // 0x1010507
     field public static final int preferenceInformationStyle = 16842893; // 0x101008d
     field public static final int preferenceLayoutChild = 16842900; // 0x1010094
     field public static final int preferenceScreenStyle = 16842891; // 0x101008b
@@ -1804,11 +1807,13 @@
     field public static final int icon = 16908294; // 0x1020006
     field public static final int icon1 = 16908295; // 0x1020007
     field public static final int icon2 = 16908296; // 0x1020008
+    field public static final int icon_frame = 16908350; // 0x102003e
     field public static final int input = 16908297; // 0x1020009
     field public static final int inputArea = 16908318; // 0x102001e
     field public static final int inputExtractEditText = 16908325; // 0x1020025
     field public static final int keyboardView = 16908326; // 0x1020026
     field public static final int list = 16908298; // 0x102000a
+    field public static final int list_container = 16908351; // 0x102003f
     field public static final int mask = 16908334; // 0x102002e
     field public static final int message = 16908299; // 0x102000b
     field public static final int navigationBarBackground = 16908336; // 0x1020030
@@ -1828,6 +1833,7 @@
     field public static final int stopSelectingText = 16908329; // 0x1020029
     field public static final int summary = 16908304; // 0x1020010
     field public static final int switchInputMethod = 16908324; // 0x1020024
+    field public static final int switch_widget = 16908352; // 0x1020040
     field public static final int tabcontent = 16908305; // 0x1020011
     field public static final int tabhost = 16908306; // 0x1020012
     field public static final int tabs = 16908307; // 0x1020013
@@ -5831,6 +5837,7 @@
     method public deprecated java.lang.String getDeviceInitializerApp();
     method public deprecated android.content.ComponentName getDeviceInitializerComponent();
     method public java.lang.String getDeviceOwner();
+    method public java.lang.String getDeviceOwnerLockScreenInfo();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
     method public int getKeyguardDisabledFeatures(android.content.ComponentName);
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
@@ -5889,6 +5896,7 @@
     method public void setCameraDisabled(android.content.ComponentName, boolean);
     method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
     method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
+    method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
     method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 1ee60b0..b208e43 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -86,6 +86,8 @@
             runBenchmark();
         } else if ("forget".equals(op)) {
             runForget();
+        } else if ("set-emulate-fbe".equals(op)) {
+            runSetEmulateFbe();
         } else {
             throw new IllegalArgumentException();
         }
@@ -137,6 +139,12 @@
                 StorageManager.DEBUG_FORCE_ADOPTABLE);
     }
 
+    public void runSetEmulateFbe() throws RemoteException {
+        final boolean emulateFbe = Boolean.parseBoolean(nextArg());
+        mSm.setDebugFlags(emulateFbe ? StorageManager.DEBUG_EMULATE_FBE : 0,
+                StorageManager.DEBUG_EMULATE_FBE);
+    }
+
     public void runPartition() throws RemoteException {
         final String diskId = nextArg();
         final String type = nextArg();
@@ -205,6 +213,8 @@
         System.err.println("");
         System.err.println("       sm forget [UUID|all]");
         System.err.println("");
+        System.err.println("       sm set-emulate-fbe [true|false]");
+        System.err.println("");
         return 1;
     }
 }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 351064a..65de4ca 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1558,6 +1558,16 @@
             readFromParcel(source);
         }
 
+        /**
+         * Resets this info state to the initial state.
+         * @hide
+         */
+        public void reset() {
+            taskWidth = 0;
+            taskHeight = 0;
+            screenOrientation = 0;
+        }
+
         /** @hide */
         public void saveToXml(XmlSerializer out) throws IOException {
             out.attribute(null, ATTR_TASK_WIDTH, Integer.toString(taskWidth));
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 65c63f6..4449e4f 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2611,6 +2611,14 @@
             return true;
         }
 
+        case UPDATE_DEVICE_OWNER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            String packageName = data.readString();
+            updateDeviceOwner(packageName);
+            reply.writeNoException();
+            return true;
+        }
+
         case GET_PACKAGE_PROCESS_STATE_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             String pkg = data.readString();
@@ -6155,6 +6163,18 @@
     }
 
     @Override
+    public void updateDeviceOwner(String packageName) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(packageName);
+        mRemote.transact(UPDATE_DEVICE_OWNER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    @Override
     public int getPackageProcessState(String packageName, String callingPackage)
             throws RemoteException {
         Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 371c923..c075ed6 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -24,8 +24,6 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.graphics.SurfaceTexture;
-import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.OperationCanceledException;
@@ -45,6 +43,17 @@
 import dalvik.system.CloseGuard;
 
 import java.lang.ref.WeakReference;
+import java.util.ArrayDeque;
+import java.util.concurrent.Executor;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.android.internal.annotations.GuardedBy;
+
 
 /** @hide */
 public class ActivityView extends ViewGroup {
@@ -53,9 +62,64 @@
 
     private static final int MSG_SET_SURFACE = 1;
 
-    DisplayMetrics mMetrics = new DisplayMetrics();
+    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
+    private static final int MINIMUM_POOL_SIZE = 1;
+    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
+    private static final int KEEP_ALIVE = 1;
+
+    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
+        private final AtomicInteger mCount = new AtomicInteger(1);
+
+        public Thread newThread(Runnable r) {
+            return new Thread(r, "ActivityView #" + mCount.getAndIncrement());
+        }
+    };
+
+    private static final BlockingQueue<Runnable> sPoolWorkQueue =
+            new LinkedBlockingQueue<Runnable>(128);
+
+    /**
+     * An {@link Executor} that can be used to execute tasks in parallel.
+     */
+    private static final Executor sExecutor = new ThreadPoolExecutor(MINIMUM_POOL_SIZE,
+            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
+
+
+    private static class SerialExecutor implements Executor {
+        private final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
+        private Runnable mActive;
+
+        public synchronized void execute(final Runnable r) {
+            mTasks.offer(new Runnable() {
+                public void run() {
+                    try {
+                        r.run();
+                    } finally {
+                        scheduleNext();
+                    }
+                }
+            });
+            if (mActive == null) {
+                scheduleNext();
+            }
+        }
+
+        protected synchronized void scheduleNext() {
+            if ((mActive = mTasks.poll()) != null) {
+                sExecutor.execute(mActive);
+            }
+        }
+    }
+
+    private final SerialExecutor mExecutor = new SerialExecutor();
+
+    private final int mDensityDpi;
     private final TextureView mTextureView;
+
+    @GuardedBy("mActivityContainerLock")
     private ActivityContainerWrapper mActivityContainer;
+    private Object mActivityContainerLock = new Object();
+
     private Activity mActivity;
     private int mWidth;
     private int mHeight;
@@ -63,8 +127,6 @@
     private int mLastVisibility;
     private ActivityViewCallback mActivityViewCallback;
 
-    private HandlerThread mThread = new HandlerThread("ActivityViewThread");
-    private Handler mHandler;
 
     public ActivityView(Context context) {
         this(context, null);
@@ -97,28 +159,14 @@
                     + e);
         }
 
-        mThread.start();
-        mHandler = new Handler(mThread.getLooper()) {
-            @Override
-            public void handleMessage(Message msg) {
-                super.handleMessage(msg);
-                if (msg.what == MSG_SET_SURFACE) {
-                    try {
-                        mActivityContainer.setSurface((Surface) msg.obj, msg.arg1, msg.arg2,
-                                mMetrics.densityDpi);
-                    } catch (RemoteException e) {
-                        throw new RuntimeException(
-                                "ActivityView: Unable to set surface of ActivityContainer. " + e);
-                    }
-                }
-            }
-        };
         mTextureView = new TextureView(context);
         mTextureView.setSurfaceTextureListener(new ActivityViewSurfaceTextureListener());
         addView(mTextureView);
 
         WindowManager wm = (WindowManager)mActivity.getSystemService(Context.WINDOW_SERVICE);
-        wm.getDefaultDisplay().getMetrics(mMetrics);
+        DisplayMetrics metrics = new DisplayMetrics();
+        wm.getDefaultDisplay().getMetrics(metrics);
+        mDensityDpi = metrics.densityDpi;
 
         mLastVisibility = getVisibility();
 
@@ -131,15 +179,13 @@
     }
 
     @Override
-    protected void onVisibilityChanged(View changedView, int visibility) {
+    protected void onVisibilityChanged(View changedView, final int visibility) {
         super.onVisibilityChanged(changedView, visibility);
 
         if (mSurface != null && (visibility == View.GONE || mLastVisibility == View.GONE)) {
-            Message msg = Message.obtain(mHandler, MSG_SET_SURFACE);
-            msg.obj = (visibility == View.GONE) ? null : mSurface;
-            msg.arg1 = mWidth;
-            msg.arg2 = mHeight;
-            mHandler.sendMessage(msg);
+            if (DEBUG) Log.v(TAG, "visibility changed; enqueing runnable");
+            final Surface surface = (visibility == View.GONE) ? null : mSurface;
+            setSurfaceAsync(surface, mWidth, mHeight, mDensityDpi, false);
         }
         mLastVisibility = visibility;
     }
@@ -230,8 +276,10 @@
             Log.e(TAG, "Duplicate call to release");
             return;
         }
-        mActivityContainer.release();
-        mActivityContainer = null;
+        synchronized (mActivityContainerLock) {
+            mActivityContainer.release();
+            mActivityContainer = null;
+        }
 
         if (mSurface != null) {
             mSurface.release();
@@ -239,25 +287,39 @@
         }
 
         mTextureView.setSurfaceTextureListener(null);
-
-        mThread.quit();
     }
 
-    private void attachToSurfaceWhenReady() {
-        final SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
-        if (surfaceTexture == null || mSurface != null) {
-            // Either not ready to attach, or already attached.
-            return;
-        }
-
-        mSurface = new Surface(surfaceTexture);
-        try {
-            mActivityContainer.setSurface(mSurface, mWidth, mHeight, mMetrics.densityDpi);
-        } catch (RemoteException e) {
-            mSurface.release();
-            mSurface = null;
-            throw new RuntimeException("ActivityView: Unable to create ActivityContainer. " + e);
-        }
+    private void setSurfaceAsync(final Surface surface, final int width, final int height,
+            final int densityDpi, final boolean callback) {
+        mExecutor.execute(new Runnable() {
+            public void run() {
+                try {
+                    synchronized (mActivityContainerLock) {
+                        if (mActivityContainer != null) {
+                            mActivityContainer.setSurface(surface, width, height, densityDpi);
+                        }
+                    }
+                } catch (RemoteException e) {
+                    throw new RuntimeException(
+                        "ActivityView: Unable to set surface of ActivityContainer. ",
+                        e);
+                }
+                if (callback) {
+                    post(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (mActivityViewCallback != null) {
+                                if (surface != null) {
+                                    mActivityViewCallback.onSurfaceAvailable(ActivityView.this);
+                                } else {
+                                    mActivityViewCallback.onSurfaceDestroyed(ActivityView.this);
+                                }
+                            }
+                        }
+                    });
+                }
+            }
+        });
     }
 
     /**
@@ -308,10 +370,8 @@
                     + height);
             mWidth = width;
             mHeight = height;
-            attachToSurfaceWhenReady();
-            if (mActivityViewCallback != null) {
-                mActivityViewCallback.onSurfaceAvailable(ActivityView.this);
-            }
+            mSurface = new Surface(surfaceTexture);
+            setSurfaceAsync(mSurface, mWidth, mHeight, mDensityDpi, true);
         }
 
         @Override
@@ -331,15 +391,7 @@
             if (DEBUG) Log.d(TAG, "onSurfaceTextureDestroyed");
             mSurface.release();
             mSurface = null;
-            try {
-                mActivityContainer.setSurface(null, mWidth, mHeight, mMetrics.densityDpi);
-            } catch (RemoteException e) {
-                throw new RuntimeException(
-                        "ActivityView: Unable to set surface of ActivityContainer. " + e);
-            }
-            if (mActivityViewCallback != null) {
-                mActivityViewCallback.onSurfaceDestroyed(ActivityView.this);
-            }
+            setSurfaceAsync(null, mWidth, mHeight, mDensityDpi, true);
             return true;
         }
 
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index cf2452b..b69a480 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -518,6 +518,7 @@
     public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake)
             throws RemoteException;
     public void updateLockTaskPackages(int userId, String[] packages) throws RemoteException;
+    public void updateDeviceOwner(String packageName) throws RemoteException;
 
     public int getPackageProcessState(String packageName, String callingPackage)
             throws RemoteException;
@@ -880,6 +881,7 @@
     int NOTE_ALARM_FINISH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+292;
     int GET_PACKAGE_PROCESS_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+293;
     int SHOW_LOCK_TASK_ESCAPE_MESSAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+294;
+    int UPDATE_DEVICE_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+295;
     int KEYGUARD_GOING_AWAY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+296;
     int REGISTER_UID_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+297;
     int UNREGISTER_UID_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+298;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b89c5a6..faed7a0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -122,7 +122,10 @@
      * Provisioning adds a managed profile and sets the MDM as the profile owner who has full
      * control over the profile.
      *
-     * In version {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this intent must contain the
+     * <p>It is possible to check if provisioning is allowed or not by querying the method
+     * {@link #isProvisioningAllowed(String)}.
+     *
+     * <p>In version {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this intent must contain the
      * extra {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}.
      * As of {@link android.os.Build.VERSION_CODES#M}, it should contain the extra
      * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME} instead, although specifying only
@@ -157,9 +160,8 @@
      * been completed. Use {@link #isProvisioningAllowed(String)} to check if provisioning is
      * allowed.
      *
-     * This intent should contain the extra {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME},
-     * although specifying only {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} is also
-     * supported.
+     * <p>This intent should contain the extra
+     * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}.
      *
      * <p> If provisioning fails, the device returns to its previous state.
      *
@@ -185,10 +187,10 @@
      * employee or client.
      *
      * <p> An intent with this action can be sent only on an unprovisioned device.
-     * It is possible to check if the device is provisioned or not by looking at
-     * {@link android.provider.Settings.Global#DEVICE_PROVISIONED}
+     * It is possible to check if provisioning is allowed or not by querying the method
+     * {@link #isProvisioningAllowed(String)}.
      *
-     * The intent contains the following extras:
+     * <p>The intent contains the following extras:
      * <ul>
      * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
      * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
@@ -212,6 +214,53 @@
         = "android.app.action.PROVISION_MANAGED_DEVICE";
 
     /**
+     * Activity action: Starts the provisioning flow which sets up a managed device.
+     * Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
+     *
+     * <p>NOTE: This is only supported on split system user devices, and puts the device into a
+     * management state that is distinct from that reached by
+     * {@link #ACTION_PROVISION_MANAGED_DEVICE} - specifically the device owner runs on the system
+     * user, and only has control over device-wide policies, not individual users and their data.
+     * The primary benefit is that multiple non-system users are supported when provisioning using
+     * this form of device management.
+     *
+     * <p> During device owner provisioning a device admin app is set as the owner of the device.
+     * A device owner has full control over the device. The device owner can not be modified by the
+     * user.
+     *
+     * <p> A typical use case would be a device that is owned by a company, but used by either an
+     * employee or client.
+     *
+     * <p> An intent with this action can be sent only on an unprovisioned device.
+     * It is possible to check if provisioning is allowed or not by querying the method
+     * {@link #isProvisioningAllowed(String)}.
+     *
+     * <p>The intent contains the following extras:
+     * <ul>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
+     * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
+     * </ul>
+     *
+     * <p> When device owner provisioning has completed, an intent of the type
+     * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcast to the
+     * device owner.
+     *
+     * <p> If provisioning fails, the device is factory reset.
+     *
+     * <p>A result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part
+     * of the provisioning flow was successful, although this doesn't guarantee the full flow will
+     * succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} implies
+     * that the user backed-out of provisioning, or some precondition for provisioning wasn't met.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE
+        = "android.app.action.PROVISION_MANAGED_SHAREABLE_DEVICE";
+
+    /**
      * A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that
      * allows a mobile device management application or NFC programmer application which starts
      * managed provisioning to pass data to the management application instance after provisioning.
@@ -595,7 +644,7 @@
      * extra field. This will invoke a UI to bring the user through adding the profile owner admin
      * to remotely control restrictions on the user.
      *
-     * <p>The intent must be invoked via {@link Activity#startActivityForResult} to receive the
+     * <p>The intent must be invoked via {@link Activity#startActivityForResult()} to receive the
      * result of whether or not the user approved the action. If approved, the result will
      * be {@link Activity#RESULT_OK} and the component will be set as an active admin as well
      * as a profile owner.
@@ -1662,7 +1711,16 @@
      * Force a new device unlock password (the password needed to access the
      * entire device, not for individual accounts) on the user.  This takes
      * effect immediately.
-     * The given password must be sufficient for the
+     *
+     * <p>Calling this from a managed profile that shares the password with the owner profile
+     * will throw a security exception.
+     *
+     * <p><em>Note: This API has been limited as of {@link android.os.Build.VERSION_CODES#N} for
+     * device admins that are not device owner and not profile owner.
+     * The password can now only be changed if there is currently no password set.  Device owner
+     * and profile owner can still do this.</em>
+     *
+     * <p>The given password must be sufficient for the
      * current password quality and length constraints as returned by
      * {@link #getPasswordQuality(ComponentName)} and
      * {@link #getPasswordMinimumLength(ComponentName)}; if it does not meet
@@ -1672,19 +1730,20 @@
      * the currently active quality will be increased to match.
      *
      * <p>Calling with a null or empty password will clear any existing PIN,
-     * pattern or password if the current password constraints allow it.
+     * pattern or password if the current password constraints allow it. <em>Note: This will not
+     * work in {@link android.os.Build.VERSION_CODES#N} and later for device admins that are not
+     * device owner and not profile owner.  Once set, the password cannot be changed to null or
+     * empty, except by device owner or profile owner.</em>
      *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} to be able to call
      * this method; if it has not, a security exception will be thrown.
      *
-     * <p>Calling this from a managed profile will throw a security exception.
-     *
      * @param password The new password for the user. Null or empty clears the password.
      * @param flags May be 0 or combination of {@link #RESET_PASSWORD_REQUIRE_ENTRY} and
      *              {@link #RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT}.
      * @return Returns true if the password was applied, or false if it is
-     * not acceptable for the current constraints.
+     * not acceptable for the current constraints or if the user has not been decrypted yet.
      */
     public boolean resetPassword(String password, int flags) {
         if (mService != null) {
@@ -1792,7 +1851,7 @@
     public void wipeData(int flags) {
         if (mService != null) {
             try {
-                mService.wipeData(flags, myUserId());
+                mService.wipeData(flags);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed talking with device policy service", e);
             }
@@ -2668,14 +2727,14 @@
      * does *not* check weather the device owner is actually running on the current user.
      */
     public boolean isDeviceOwnerApp(String packageName) {
-        if (mService != null) {
-            try {
-                return mService.isDeviceOwnerPackage(packageName);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed talking with device policy service", e);
-            }
+        if (packageName == null) {
+            return false;
         }
-        return false;
+        final ComponentName deviceOwner = getDeviceOwnerComponent();
+        if (deviceOwner == null) {
+            return false;
+        }
+        return packageName.equals(deviceOwner.getPackageName());
     }
 
     /**
@@ -2869,9 +2928,6 @@
      */
     public boolean setProfileOwner(@NonNull ComponentName admin, @Deprecated String ownerName,
             int userHandle) throws IllegalArgumentException {
-        if (admin == null) {
-            throw new NullPointerException("admin cannot be null");
-        }
         if (mService != null) {
             try {
                 if (ownerName == null) {
@@ -2887,6 +2943,42 @@
     }
 
     /**
+     * Sets the device owner information to be shown on the lock screen.
+     *
+     * <p>If the device owner information is {@code null} or empty then the device owner info is
+     * cleared and the user owner info is shown on the lock screen if it is set.
+     *
+     * @param admin The name of the admin component to check.
+     * @param info Device owner information which will be displayed instead of the user
+     * owner info.
+     * @return Whether the device owner information has been set.
+     */
+    public boolean setDeviceOwnerLockScreenInfo(@NonNull ComponentName admin, String info) {
+        if (mService != null) {
+            try {
+                return mService.setDeviceOwnerLockScreenInfo(admin, info);
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed talking with device policy service", re);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return The device owner information. If it is not set returns {@code null}.
+     */
+    public String getDeviceOwnerLockScreenInfo() {
+        if (mService != null) {
+            try {
+                return mService.getDeviceOwnerLockScreenInfo();
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed talking with device policy service", re);
+            }
+        }
+        return null;
+    }
+
+    /**
      * Sets the enabled state of the profile. A profile should be enabled only once it is ready to
      * be used. Only the profile owner can call this.
      *
@@ -2996,7 +3088,7 @@
 
     /**
      * @hide
-     * @param userId The user for whom to fetch the profile owner name, if any.
+     * @param user The user for whom to fetch the profile owner name, if any.
      * @return the human readable name of the organisation associated with this profile owner or
      *         null if one is not set.
      * @throws IllegalArgumentException if the userId is invalid.
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 5a46cd5..4270e16 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -80,10 +80,4 @@
      * This method always returns a new {@link Bundle}.
      */
     public abstract Bundle getComposedUserRestrictions(int userId, Bundle inBundle);
-
-    /**
-     * @return true if a package is a device admin (possibly DO or PO) running on
-     * user {@code userId}.
-     */
-    public abstract boolean isDeviceAdminPackage(int userId, String packageName);
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 95a22ef..7601cf2 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -81,7 +81,7 @@
 
     void lockNow();
 
-    void wipeData(int flags, int userHandle);
+    void wipeData(int flags);
 
     ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
     ComponentName getGlobalProxyAdmin(int userHandle);
@@ -114,7 +114,6 @@
     void reportSuccessfulPasswordAttempt(int userHandle);
 
     boolean setDeviceOwner(in ComponentName who, String ownerName, int userId);
-    boolean isDeviceOwnerPackage(String packageName);
     ComponentName getDeviceOwner();
     String getDeviceOwnerName();
     void clearDeviceOwner(String packageName);
@@ -127,6 +126,9 @@
     void clearProfileOwner(in ComponentName who);
     boolean hasUserSetupCompleted();
 
+    boolean setDeviceOwnerLockScreenInfo(in ComponentName who, String deviceOwnerInfo);
+    String getDeviceOwnerLockScreenInfo();
+
     boolean installCaCert(in ComponentName admin, in byte[] certBuffer);
     void uninstallCaCerts(in ComponentName admin, in String[] aliases);
     void enforceCanManageCaCerts(in ComponentName admin);
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 52c2f9b..9c880d3 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -22,7 +22,9 @@
 import android.os.Environment;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.storage.StorageManager;
 import android.text.TextUtils;
 import android.util.Printer;
 
@@ -469,6 +471,14 @@
     public static final int PRIVATE_FLAG_FORCE_DEVICE_ENCRYPTED = 1 << 5;
 
     /**
+     * When set, assume that all components under the given app are encryption
+     * aware, unless otherwise specified.
+     *
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_ENCRYPTION_AWARE = 1 << 6;
+
+    /**
      * Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
      * {@hide}
      */
@@ -963,7 +973,8 @@
                 .getDataUserCredentialEncryptedPackageDirectory(volumeUuid, userId, packageName)
                 .getAbsolutePath();
 
-        if ((privateFlags & PRIVATE_FLAG_FORCE_DEVICE_ENCRYPTED) != 0) {
+        if ((privateFlags & PRIVATE_FLAG_FORCE_DEVICE_ENCRYPTED) != 0
+                && SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false)) {
             dataDir = deviceEncryptedDataDir;
         } else {
             dataDir = credentialEncryptedDataDir;
@@ -1030,6 +1041,11 @@
                 && (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
     }
 
+    /** @hide */
+    public boolean isEncryptionAware() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ENCRYPTION_AWARE) != 0;
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/content/pm/AppsQueryHelper.java b/core/java/android/content/pm/AppsQueryHelper.java
index a5a8e3f..084bc18 100644
--- a/core/java/android/content/pm/AppsQueryHelper.java
+++ b/core/java/android/content/pm/AppsQueryHelper.java
@@ -18,13 +18,11 @@
 
 import android.Manifest;
 import android.app.AppGlobals;
-import android.content.Context;
 import android.content.Intent;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArraySet;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethod;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -44,36 +42,37 @@
     public static int GET_NON_LAUNCHABLE_APPS = 1;
 
     /**
-     * Return apps with {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission
+     * Return apps with {@link Manifest.permission#INTERACT_ACROSS_USERS} permission
      */
     public static int GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM = 1 << 1;
 
     /**
-     * Return all input methods that are marked as default.
-     * <p>When this flag is set, {@code user} specified in
-     * {@link #queryApps(int, boolean, UserHandle)} must be
-     * {@link UserHandle#myUserId user of the current process}.
+     * Return all input methods available for the current user.
      */
-    public static int GET_DEFAULT_IMES = 1 << 2;
+    public static int GET_IMES = 1 << 2;
 
-    private final Context mContext;
+    private final IPackageManager mPackageManager;
     private List<ApplicationInfo> mAllApps;
 
-    public AppsQueryHelper(Context context) {
-        mContext = context;
+    public AppsQueryHelper(IPackageManager packageManager) {
+        mPackageManager = packageManager;
+    }
+
+    public AppsQueryHelper() {
+        this(AppGlobals.getPackageManager());
     }
 
     /**
      * Return a List of all packages that satisfy a specified criteria.
      * @param flags search flags. Use any combination of {@link #GET_NON_LAUNCHABLE_APPS},
-     * {@link #GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM} or {@link #GET_DEFAULT_IMES}.
+     * {@link #GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM} or {@link #GET_IMES}.
      * @param systemAppsOnly if true, only system apps will be returned
      * @param user user, whose apps are queried
      */
     public List<String> queryApps(int flags, boolean systemAppsOnly, UserHandle user) {
         boolean nonLaunchableApps = (flags & GET_NON_LAUNCHABLE_APPS) > 0;
         boolean interactAcrossUsers = (flags & GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM) > 0;
-        boolean defaultImes = (flags & GET_DEFAULT_IMES) > 0;
+        boolean imes = (flags & GET_IMES) > 0;
         if (mAllApps == null) {
             mAllApps = getAllApps(user.getIdentifier());
         }
@@ -113,7 +112,6 @@
                 }
             }
         }
-
         if (interactAcrossUsers) {
             final List<PackageInfo> packagesHoldingPermissions = getPackagesHoldingPermission(
                     Manifest.permission.INTERACT_ACROSS_USERS, user.getIdentifier());
@@ -129,29 +127,18 @@
             }
         }
 
-        if (defaultImes) {
-            if (UserHandle.myUserId() != user.getIdentifier()) {
-                throw new IllegalArgumentException("Specified user handle " + user
-                        + " is not a user of the current process.");
-            }
-            List<InputMethodInfo> imis = getInputMethodList();
-            int imisSize = imis.size();
-            ArraySet<String> defaultImePackages = new ArraySet<>();
-            for (int i = 0; i < imisSize; i++) {
-                InputMethodInfo imi = imis.get(i);
-                if (imi.isDefault(mContext)) {
-                    defaultImePackages.add(imi.getPackageName());
-                }
-            }
-            final int allAppsSize = mAllApps.size();
-            for (int i = 0; i < allAppsSize; i++) {
-                final ApplicationInfo appInfo = mAllApps.get(i);
-                if (systemAppsOnly && !appInfo.isSystemApp()) {
+        if (imes) {
+            final List<ResolveInfo> resolveInfos = queryIntentServicesAsUser(
+                    new Intent(InputMethod.SERVICE_INTERFACE), user.getIdentifier());
+            final int resolveInfosSize = resolveInfos.size();
+
+            for (int i = 0; i < resolveInfosSize; i++) {
+                ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
+                if (systemAppsOnly && !serviceInfo.applicationInfo.isSystemApp()) {
                     continue;
                 }
-                final String packageName = appInfo.packageName;
-                if (defaultImePackages.contains(packageName)) {
-                    result.add(packageName);
+                if (!result.contains(serviceInfo.packageName)) {
+                    result.add(serviceInfo.packageName);
                 }
             }
         }
@@ -163,9 +150,8 @@
     @SuppressWarnings("unchecked")
     protected List<ApplicationInfo> getAllApps(int userId) {
         try {
-            return AppGlobals.getPackageManager().getInstalledApplications(
-                    PackageManager.GET_UNINSTALLED_PACKAGES
-                            | PackageManager.GET_DISABLED_COMPONENTS, userId).getList();
+            return mPackageManager.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES
+                    | PackageManager.GET_DISABLED_COMPONENTS, userId).getList();
         } catch (RemoteException e) {
             throw new IllegalStateException("Package manager has died", e);
         }
@@ -173,17 +159,22 @@
 
     @VisibleForTesting
     protected List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int userId) {
-        return mContext.getPackageManager()
-                .queryIntentActivitiesAsUser(intent, PackageManager.GET_DISABLED_COMPONENTS
-                        | PackageManager.GET_UNINSTALLED_PACKAGES, userId);
+        try {
+            return mPackageManager.queryIntentActivities(intent, null,
+                    PackageManager.GET_DISABLED_COMPONENTS
+                            | PackageManager.GET_UNINSTALLED_PACKAGES,
+                    userId);
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Package manager has died", e);
+        }
     }
 
     @VisibleForTesting
-    @SuppressWarnings("unchecked")
-    protected List<PackageInfo> getPackagesHoldingPermission(String perm, int userId) {
+    protected List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int userId) {
         try {
-            return AppGlobals.getPackageManager().getPackagesHoldingPermissions(new String[]{perm},
-                    0, userId).getList();
+            return mPackageManager.queryIntentServices(intent, null,
+                    PackageManager.GET_META_DATA
+                            | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId);
         } catch (RemoteException e) {
             throw new IllegalStateException("Package manager has died", e);
         }
@@ -191,9 +182,13 @@
 
     @VisibleForTesting
     @SuppressWarnings("unchecked")
-    protected List<InputMethodInfo> getInputMethodList() {
-        InputMethodManager imm = (InputMethodManager)
-                mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
-        return imm.getInputMethodList();
+    protected List<PackageInfo> getPackagesHoldingPermission(String perm, int userId) {
+        try {
+            return mPackageManager.getPackagesHoldingPermissions(new String[]{perm}, 0,
+                    userId).getList();
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Package manager has died", e);
+        }
     }
+
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index f176d89..838da37 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2636,6 +2636,10 @@
                 && (flags & PARSE_IS_SYSTEM) != 0) {
             ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_FORCE_DEVICE_ENCRYPTED;
         }
+        if (sa.getBoolean(R.styleable.AndroidManifestApplication_encryptionAware, false)
+                && (flags & PARSE_IS_SYSTEM) != 0) {
+            ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ENCRYPTION_AWARE;
+        }
 
         String str;
         str = sa.getNonConfigurationString(
@@ -3236,7 +3240,8 @@
                     sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0);
 
             a.info.encryptionAware = sa.getBoolean(
-                    R.styleable.AndroidManifestActivity_encryptionAware, false);
+                    R.styleable.AndroidManifestActivity_encryptionAware,
+                    owner.applicationInfo.isEncryptionAware());
         } else {
             a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
             a.info.configChanges = 0;
@@ -3253,7 +3258,8 @@
             }
 
             a.info.encryptionAware = sa.getBoolean(
-                    R.styleable.AndroidManifestActivity_encryptionAware, false);
+                    R.styleable.AndroidManifestActivity_encryptionAware,
+                    owner.applicationInfo.isEncryptionAware());
         }
 
         sa.recycle();
@@ -3655,7 +3661,8 @@
         }
 
         p.info.encryptionAware = sa.getBoolean(
-                R.styleable.AndroidManifestProvider_encryptionAware, false);
+                R.styleable.AndroidManifestProvider_encryptionAware,
+                owner.applicationInfo.isEncryptionAware());
 
         sa.recycle();
 
@@ -3938,7 +3945,8 @@
         }
 
         s.info.encryptionAware = sa.getBoolean(
-                R.styleable.AndroidManifestService_encryptionAware, false);
+                R.styleable.AndroidManifestService_encryptionAware,
+                owner.applicationInfo.isEncryptionAware());
 
         sa.recycle();
 
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 1fc69c0..73bb426 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -3182,8 +3182,8 @@
         }
 
         /**
-         * Sets GPS processing method. It will store up to 32 characters
-         * in JPEG EXIF header.
+         * Sets GPS processing method. The method will be stored in a UTF-8 string up to 31 bytes
+         * long, in the JPEG EXIF header.
          *
          * @param processing_method The processing method to get this location.
          */
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 35a1d96..f61892e 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -472,13 +472,13 @@
      * <li>The maximum available resolution for RAW_SENSOR streams
      *   will match either the value in
      *   {@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE android.sensor.info.pixelArraySize} or
-     *   {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.</li>
+     *   {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.</li>
      * <li>All DNG-related optional metadata entries are provided
      *   by the camera device.</li>
      * </ul>
      *
-     * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
      * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
      */
     public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3;
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 3f566eb..67835a0 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -556,6 +556,10 @@
          * Set a capture request field to a value. The field definitions can be
          * found in {@link CaptureRequest}.
          *
+         * <p>Setting a field to {@code null} will remove that field from the capture request.
+         * Unless the field is optional, removing it will likely produce an error from the camera
+         * device when the request is submitted.</p>
+         *
          * @param key The metadata field to write.
          * @param value The value to set the field to, which must be of a matching
          * type to the key.
diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java
index 25ef8b5..6729347 100644
--- a/core/java/android/net/http/X509TrustManagerExtensions.java
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -20,6 +20,9 @@
 
 import com.android.org.conscrypt.TrustManagerImpl;
 
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.List;
@@ -36,7 +39,11 @@
  */
 public class X509TrustManagerExtensions {
 
-    final TrustManagerImpl mDelegate;
+    private final TrustManagerImpl mDelegate;
+    // Methods to use when mDelegate is not a TrustManagerImpl and duck typing is being used.
+    private final X509TrustManager mTrustManager;
+    private final Method mCheckServerTrusted;
+    private final Method mIsUserAddedCertificate;
 
     /**
      * Constructs a new X509TrustManagerExtensions wrapper.
@@ -47,10 +54,31 @@
     public X509TrustManagerExtensions(X509TrustManager tm) throws IllegalArgumentException {
         if (tm instanceof TrustManagerImpl) {
             mDelegate = (TrustManagerImpl) tm;
-        } else {
-            mDelegate = null;
-            throw new IllegalArgumentException("tm is an instance of " + tm.getClass().getName() +
-                    " which is not a supported type of X509TrustManager");
+            mTrustManager = null;
+            mCheckServerTrusted = null;
+            mIsUserAddedCertificate = null;
+            return;
+        }
+        // Use duck typing if possible.
+        mDelegate = null;
+        mTrustManager = tm;
+        // Check that the hostname aware checkServerTrusted is present.
+        try {
+            mCheckServerTrusted = tm.getClass().getMethod("checkServerTrusted",
+                    X509Certificate[].class,
+                    String.class,
+                    String.class);
+        } catch (NoSuchMethodException e) {
+            throw new IllegalArgumentException("Required method"
+                    + " checkServerTrusted(X509Certificate[], String, String, String) missing");
+        }
+        // Check that isUserAddedCertificate is present.
+        try {
+            mIsUserAddedCertificate = tm.getClass().getMethod("isUserAddedCertificate",
+                    X509Certificate.class);
+        } catch (NoSuchMethodException e) {
+            throw new IllegalArgumentException(
+                    "Required method isUserAddedCertificate(X509Certificate) missing");
         }
     }
 
@@ -66,7 +94,24 @@
      */
     public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType,
                                                     String host) throws CertificateException {
-        return mDelegate.checkServerTrusted(chain, authType, host);
+        if (mDelegate != null) {
+            return mDelegate.checkServerTrusted(chain, authType, host);
+        } else {
+            try {
+                return (List<X509Certificate>) mCheckServerTrusted.invoke(mTrustManager, chain,
+                        authType, host);
+            } catch (IllegalAccessException e) {
+                throw new CertificateException("Failed to call checkServerTrusted", e);
+            } catch (InvocationTargetException e) {
+                if (e.getCause() instanceof CertificateException) {
+                    throw (CertificateException) e.getCause();
+                }
+                if (e.getCause() instanceof RuntimeException) {
+                    throw (RuntimeException) e.getCause();
+                }
+                throw new CertificateException("checkServerTrusted failed", e.getCause());
+            }
+        }
     }
 
     /**
@@ -80,7 +125,21 @@
      * otherwise.
      */
     public boolean isUserAddedCertificate(X509Certificate cert) {
-        return mDelegate.isUserAddedCertificate(cert);
+        if (mDelegate != null) {
+            return mDelegate.isUserAddedCertificate(cert);
+        } else {
+            try {
+                return (Boolean) mIsUserAddedCertificate.invoke(mTrustManager, cert);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException("Failed to call isUserAddedCertificate", e);
+            } catch (InvocationTargetException e) {
+                if (e.getCause() instanceof RuntimeException) {
+                    throw (RuntimeException) e.getCause();
+                } else {
+                    throw new RuntimeException("isUserAddedCertificate failed", e.getCause());
+                }
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 5852f5f..f2aea08 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -624,6 +624,32 @@
     }
 
     /**
+     * {@hide}
+     * This will be the new name for writeFileDescriptor, for consistency.
+     **/
+    public final void writeRawFileDescriptor(FileDescriptor val) {
+        nativeWriteFileDescriptor(mNativePtr, val);
+    }
+
+    /**
+     * {@hide}
+     * Write an array of FileDescriptor objects into the Parcel.
+     *
+     * @param value The array of objects to be written.
+     */
+    public final void writeRawFileDescriptorArray(FileDescriptor[] value) {
+        if (value != null) {
+            int N = value.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeRawFileDescriptor(value[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    /**
      * Write a byte value into the parcel at the current dataPosition(),
      * growing dataCapacity() if needed.
      */
@@ -1700,6 +1726,41 @@
         return nativeReadFileDescriptor(mNativePtr);
     }
 
+    /**
+     * {@hide}
+     * Read and return a new array of FileDescriptors from the parcel.
+     * @return the FileDescriptor array, or null if the array is null.
+     **/
+    public final FileDescriptor[] createRawFileDescriptorArray() {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        FileDescriptor[] f = new FileDescriptor[N];
+        for (int i = 0; i < N; i++) {
+            f[i] = readRawFileDescriptor();
+        }
+        return f;
+    }
+
+    /**
+     * {@hide}
+     * Read an array of FileDescriptors from a parcel.
+     * The passed array must be exactly the length of the array in the parcel.
+     * @return the FileDescriptor array, or null if the array is null.
+     **/
+    public final void readRawFileDescriptorArray(FileDescriptor[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readRawFileDescriptor();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+
     /*package*/ static native FileDescriptor openFileDescriptor(String file,
             int mode) throws FileNotFoundException;
     /*package*/ static native FileDescriptor dupFileDescriptor(FileDescriptor orig)
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 5d45ade..d19c7c9 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -1198,14 +1198,14 @@
             }
 
             @Override
-            public void createNewUserDir(int userHandle, String path) throws RemoteException {
+            public void createUserKey(int userId, int serialNumber) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
-                    _data.writeInt(userHandle);
-                    _data.writeString(path);
-                    mRemote.transact(Stub.TRANSACTION_createNewUserDir, _data, _reply, 0);
+                    _data.writeInt(userId);
+                    _data.writeInt(serialNumber);
+                    mRemote.transact(Stub.TRANSACTION_createUserKey, _data, _reply, 0);
                     _reply.readException();
                 } finally {
                     _reply.recycle();
@@ -1214,13 +1214,81 @@
             }
 
             @Override
-            public void deleteUserKey(int userHandle) throws RemoteException {
+            public void destroyUserKey(int userId) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
-                    _data.writeInt(userHandle);
-                    mRemote.transact(Stub.TRANSACTION_deleteUserKey, _data, _reply, 0);
+                    _data.writeInt(userId);
+                    mRemote.transact(Stub.TRANSACTION_destroyUserKey, _data, _reply, 0);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
+
+            @Override
+            public void unlockUserKey(int userId, int serialNumber, byte[] token) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeInt(userId);
+                    _data.writeInt(serialNumber);
+                    _data.writeByteArray(token);
+                    mRemote.transact(Stub.TRANSACTION_unlockUserKey, _data, _reply, 0);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
+
+            @Override
+            public void lockUserKey(int userId) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeInt(userId);
+                    mRemote.transact(Stub.TRANSACTION_lockUserKey, _data, _reply, 0);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
+
+            @Override
+            public boolean isUserKeyUnlocked(int userId) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                boolean _result;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeInt(userId);
+                    mRemote.transact(Stub.TRANSACTION_isUserKeyUnlocked, _data, _reply, 0);
+                    _reply.readException();
+                    _result = 0 != _reply.readInt();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
+
+            @Override
+            public void prepareUserStorage(String volumeUuid, int userId, int serialNumber)
+                    throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeString(volumeUuid);
+                    _data.writeInt(userId);
+                    _data.writeInt(serialNumber);
+                    mRemote.transact(Stub.TRANSACTION_prepareUserStorage, _data, _reply, 0);
                     _reply.readException();
                 } finally {
                     _reply.recycle();
@@ -1359,13 +1427,17 @@
         static final int TRANSACTION_benchmark = IBinder.FIRST_CALL_TRANSACTION + 59;
         static final int TRANSACTION_setDebugFlags = IBinder.FIRST_CALL_TRANSACTION + 60;
 
-        static final int TRANSACTION_createNewUserDir = IBinder.FIRST_CALL_TRANSACTION + 62;
+        static final int TRANSACTION_createUserKey = IBinder.FIRST_CALL_TRANSACTION + 61;
+        static final int TRANSACTION_destroyUserKey = IBinder.FIRST_CALL_TRANSACTION + 62;
 
-        static final int TRANSACTION_deleteUserKey = IBinder.FIRST_CALL_TRANSACTION + 63;
+        static final int TRANSACTION_unlockUserKey = IBinder.FIRST_CALL_TRANSACTION + 63;
+        static final int TRANSACTION_lockUserKey = IBinder.FIRST_CALL_TRANSACTION + 64;
+        static final int TRANSACTION_isUserKeyUnlocked = IBinder.FIRST_CALL_TRANSACTION + 65;
 
-        static final int TRANSACTION_isPerUserEncryptionEnabled = IBinder.FIRST_CALL_TRANSACTION + 64;
+        static final int TRANSACTION_prepareUserStorage = IBinder.FIRST_CALL_TRANSACTION + 66;
 
-        static final int TRANSACTION_isConvertibleToFBE = IBinder.FIRST_CALL_TRANSACTION + 65;
+        static final int TRANSACTION_isPerUserEncryptionEnabled = IBinder.FIRST_CALL_TRANSACTION + 67;
+        static final int TRANSACTION_isConvertibleToFBE = IBinder.FIRST_CALL_TRANSACTION + 68;
 
         /**
          * Cast an IBinder object into an IMountService interface, generating a
@@ -1929,18 +2001,51 @@
                     reply.writeNoException();
                     return true;
                 }
-                case TRANSACTION_createNewUserDir: {
+                case TRANSACTION_createUserKey: {
                     data.enforceInterface(DESCRIPTOR);
-                    int userHandle = data.readInt();
-                    String path = data.readString();
-                    createNewUserDir(userHandle, path);
+                    int userId = data.readInt();
+                    int serialNumber = data.readInt();
+                    createUserKey(userId, serialNumber);
                     reply.writeNoException();
                     return true;
                 }
-                case TRANSACTION_deleteUserKey: {
+                case TRANSACTION_destroyUserKey: {
                     data.enforceInterface(DESCRIPTOR);
-                    int userHandle = data.readInt();
-                    deleteUserKey(userHandle);
+                    int userId = data.readInt();
+                    destroyUserKey(userId);
+                    reply.writeNoException();
+                    return true;
+                }
+                case TRANSACTION_unlockUserKey: {
+                    data.enforceInterface(DESCRIPTOR);
+                    int userId = data.readInt();
+                    int serialNumber = data.readInt();
+                    byte[] token = data.createByteArray();
+                    unlockUserKey(userId, serialNumber, token);
+                    reply.writeNoException();
+                    return true;
+                }
+                case TRANSACTION_lockUserKey: {
+                    data.enforceInterface(DESCRIPTOR);
+                    int userId = data.readInt();
+                    lockUserKey(userId);
+                    reply.writeNoException();
+                    return true;
+                }
+                case TRANSACTION_isUserKeyUnlocked: {
+                    data.enforceInterface(DESCRIPTOR);
+                    int userId = data.readInt();
+                    boolean result = isUserKeyUnlocked(userId);
+                    reply.writeNoException();
+                    reply.writeInt(result ? 1 : 0);
+                    return true;
+                }
+                case TRANSACTION_prepareUserStorage: {
+                    data.enforceInterface(DESCRIPTOR);
+                    String volumeUuid = data.readString();
+                    int userId = data.readInt();
+                    int serialNumber = data.readInt();
+                    prepareUserStorage(volumeUuid, userId, serialNumber);
                     reply.writeNoException();
                     return true;
                 }
@@ -1948,7 +2053,7 @@
                     data.enforceInterface(DESCRIPTOR);
                     boolean result = isPerUserEncryptionEnabled();
                     reply.writeNoException();
-                    reply.writeInt((result ? 1 : 0));
+                    reply.writeInt(result ? 1 : 0);
                     return true;
                 }
             }
@@ -2263,24 +2368,15 @@
     public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback)
             throws RemoteException;
 
-    /**
-     * Creates the user data directory, possibly encrypted
-     * @param userHandle Handle of the user whose directory we are creating
-     * @param path Path at which to create the directory.
-     */
-    public void createNewUserDir(int userHandle, String path)
-        throws RemoteException;
+    public void createUserKey(int userId, int serialNumber) throws RemoteException;
+    public void destroyUserKey(int userId) throws RemoteException;
 
-    /**
-     * Securely delete the user's encryption key
-     * @param userHandle Handle of the user whose key we are deleting
-     */
-    public void deleteUserKey(int userHandle)
-        throws RemoteException;
+    public void unlockUserKey(int userId, int serialNumber, byte[] token) throws RemoteException;
+    public void lockUserKey(int userId) throws RemoteException;
+    public boolean isUserKeyUnlocked(int userId) throws RemoteException;
 
-    /**
-     * Returns whether the current encryption type is per user.
-     */
-    public boolean isPerUserEncryptionEnabled()
-        throws RemoteException;
+    public void prepareUserStorage(String volumeUuid, int userId, int serialNumber)
+            throws RemoteException;
+
+    public boolean isPerUserEncryptionEnabled() throws RemoteException;
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2ca0b20..27df46d 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -76,7 +76,11 @@
     /** {@hide} */
     public static final String PROP_HAS_ADOPTABLE = "vold.has_adoptable";
     /** {@hide} */
+    public static final String PROP_HAS_FBE = "vold.has_fbe";
+    /** {@hide} */
     public static final String PROP_FORCE_ADOPTABLE = "persist.fw.force_adoptable";
+    /** {@hide} */
+    public static final String PROP_EMULATE_FBE = "vold.emulate_fbe";
 
     /** {@hide} */
     public static final String UUID_PRIVATE_INTERNAL = null;
@@ -85,6 +89,8 @@
 
     /** {@hide} */
     public static final int DEBUG_FORCE_ADOPTABLE = 1 << 0;
+    /** {@hide} */
+    public static final int DEBUG_EMULATE_FBE = 1 << 1;
 
     /** {@hide} */
     public static final int FLAG_FOR_WRITE = 1 << 0;
@@ -960,18 +966,54 @@
     }
 
     /** {@hide} */
-    public void createNewUserDir(int userHandle, File path) {
+    public void createUserKey(int userId, int serialNumber) {
         try {
-            mMountService.createNewUserDir(userHandle, path.getAbsolutePath());
+            mMountService.createUserKey(userId, serialNumber);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
     }
 
     /** {@hide} */
-    public void deleteUserKey(int userHandle) {
+    public void destroyUserKey(int userId) {
         try {
-            mMountService.deleteUserKey(userHandle);
+            mMountService.destroyUserKey(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
+    public void unlockUserKey(int userId, int serialNumber, byte[] token) {
+        try {
+            mMountService.unlockUserKey(userId, serialNumber, token);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
+    public void lockUserKey(int userId) {
+        try {
+            mMountService.lockUserKey(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
+    public void prepareUserStorage(String volumeUuid, int userId, int serialNumber) {
+        try {
+            mMountService.prepareUserStorage(volumeUuid, userId, serialNumber);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
+    public boolean isUserKeyUnlocked(int userId) {
+        try {
+            return mMountService.isUserKeyUnlocked(userId);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index db04c71..3e496b6 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -23,14 +23,15 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.View.OnKeyListener;
+import android.view.ViewGroup;
 import android.widget.ListView;
 
 /**
@@ -179,6 +180,27 @@
     }
 
     @Override
+    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        TypedArray a = getActivity().obtainStyledAttributes(null,
+                com.android.internal.R.styleable.PreferenceFragment,
+                com.android.internal.R.attr.preferenceFragmentStyle,
+                0);
+
+        ListView lv = (ListView) view.findViewById(android.R.id.list);
+        if (lv != null) {
+            Drawable divider =
+                    a.getDrawable(com.android.internal.R.styleable.PreferenceFragment_divider);
+            if (divider != null) {
+                lv.setDivider(divider);
+            }
+        }
+
+        a.recycle();
+    }
+
+    @Override
     public void onActivityCreated(@Nullable Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
 
diff --git a/core/java/android/preference/SwitchPreference.java b/core/java/android/preference/SwitchPreference.java
index 9c3cefc..aa8674e 100644
--- a/core/java/android/preference/SwitchPreference.java
+++ b/core/java/android/preference/SwitchPreference.java
@@ -122,7 +122,7 @@
     protected void onBindView(View view) {
         super.onBindView(view);
 
-        View checkableView = view.findViewById(com.android.internal.R.id.switchWidget);
+        View checkableView = view.findViewById(com.android.internal.R.id.switch_widget);
         if (checkableView != null && checkableView instanceof Checkable) {
             if (checkableView instanceof Switch) {
                 final Switch switchView = (Switch) checkableView;
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index 90d30d6..2afbb99 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -60,7 +60,7 @@
     private Margins mMinMargins;
 
     private int mColorMode;
-    private int mDuplexMode = DUPLEX_MODE_NONE;
+    private int mDuplexMode;
 
     PrintAttributes() {
         /* hide constructor */
@@ -403,7 +403,7 @@
         mResolution = null;
         mMinMargins = null;
         mColorMode = 0;
-        mDuplexMode = DUPLEX_MODE_NONE;
+        mDuplexMode = 0;
     }
 
     /**
@@ -1427,10 +1427,6 @@
 
         /**
          * Creates a new {@link PrintAttributes} instance.
-         * <p>
-         * If you do not specify a duplex mode, the default
-         * {@link #DUPLEX_MODE_NONE} will be used.
-         * </p>
          *
          * @return The new instance.
          */
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 503854e..8906f9b 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -41,7 +41,7 @@
     private final List<CertificatesEntryRef> mCertificatesEntryRefs;
     private Set<TrustAnchor> mAnchors;
     private final Object mAnchorsLock = new Object();
-    private X509TrustManager mTrustManager;
+    private NetworkSecurityTrustManager mTrustManager;
     private final Object mTrustManagerLock = new Object();
 
     private NetworkSecurityConfig(boolean cleartextTrafficPermitted, boolean hstsEnforced,
@@ -78,7 +78,7 @@
         return mPins;
     }
 
-    public X509TrustManager getTrustManager() {
+    public NetworkSecurityTrustManager getTrustManager() {
         synchronized(mTrustManagerLock) {
             if (mTrustManager == null) {
                 mTrustManager = new NetworkSecurityTrustManager(this);
diff --git a/core/java/android/security/net/config/NetworkSecurityTrustManager.java b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
index e69082d..7f5b3ca 100644
--- a/core/java/android/security/net/config/NetworkSecurityTrustManager.java
+++ b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
@@ -71,9 +71,28 @@
     @Override
     public void checkServerTrusted(X509Certificate[] certs, String authType)
             throws CertificateException {
-        List<X509Certificate> trustedChain =
-                mDelegate.checkServerTrusted(certs, authType, (String) null);
+        checkServerTrusted(certs, authType, null);
+    }
+
+    /**
+     * Hostname aware version of {@link #checkServerTrusted(X509Certificate[], String)}.
+     * This interface is used by conscrypt and android.net.http.X509TrustManagerExtensions do not
+     * modify without modifying those callers.
+     */
+    public List<X509Certificate> checkServerTrusted(X509Certificate[] certs, String authType,
+            String host) throws CertificateException {
+        List<X509Certificate> trustedChain = mDelegate.checkServerTrusted(certs, authType, host);
         checkPins(trustedChain);
+        return trustedChain;
+    }
+
+    /**
+     * Check if the provided certificate is a user added certificate authority.
+     * This is required by android.net.http.X509TrustManagerExtensions.
+     */
+    public boolean isUserAddedCertificate(X509Certificate cert) {
+        // TODO: Figure out the right way to handle this, and if it is still even used.
+        return false;
     }
 
     private void checkPins(List<X509Certificate> chain) throws CertificateException {
diff --git a/core/java/android/security/net/config/RootTrustManager.java b/core/java/android/security/net/config/RootTrustManager.java
index 1338b9f..b87bf1f 100644
--- a/core/java/android/security/net/config/RootTrustManager.java
+++ b/core/java/android/security/net/config/RootTrustManager.java
@@ -18,6 +18,7 @@
 
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
+import java.util.List;
 
 import javax.net.ssl.X509TrustManager;
 
@@ -61,10 +62,24 @@
         config.getTrustManager().checkServerTrusted(certs, authType);
     }
 
-    public void checkServerTrusted(X509Certificate[] certs, String authType, String hostname)
-            throws CertificateException {
+    /**
+     * Hostname aware version of {@link #checkServerTrusted(X509Certificate[], String)}.
+     * This interface is used by conscrypt and android.net.http.X509TrustManagerExtensions do not
+     * modify without modifying those callers.
+     */
+    public List<X509Certificate> checkServerTrusted(X509Certificate[] certs, String authType,
+            String hostname) throws CertificateException {
         NetworkSecurityConfig config = mConfig.getConfigForHostname(hostname);
-        config.getTrustManager().checkServerTrusted(certs, authType);
+        return config.getTrustManager().checkServerTrusted(certs, authType, hostname);
+    }
+
+    /**
+     * Check if the provided certificate is a user added certificate authority.
+     * This is required by android.net.http.X509TrustManagerExtensions.
+     */
+    public boolean isUserAddedCertificate(X509Certificate cert) {
+        // TODO: Figure out the right way to handle this, and if it is still even used.
+        return false;
     }
 
     @Override
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
index cebb8f0..f099479 100644
--- a/core/java/android/util/PathParser.java
+++ b/core/java/android/util/PathParser.java
@@ -27,21 +27,21 @@
     static final String LOGTAG = PathParser.class.getSimpleName();
 
     /**
-     * @param pathData The string representing a path, the same as "d" string in svg file.
+     * @param pathString The string representing a path, the same as "d" string in svg file.
      * @return the generated Path object.
      */
-    public static Path createPathFromPathData(String pathData) {
-        Path path = new Path();
-        PathDataNode[] nodes = createNodesFromPathData(pathData);
-        if (nodes != null) {
-            try {
-                PathDataNode.nodesToPath(nodes, path);
-            } catch (RuntimeException e) {
-                throw new RuntimeException("Error in parsing " + pathData, e);
-            }
-            return path;
+    public static Path createPathFromPathData(String pathString) {
+        if (pathString == null) {
+            throw new IllegalArgumentException("Path string can not be null.");
         }
-        return null;
+        Path path = new Path();
+        boolean hasValidPathData = nParseStringForPath(path.mNativePath, pathString,
+                pathString.length());
+        if (!hasValidPathData) {
+            throw new IllegalArgumentException("Path string: " + pathString +
+                    " does not contain valid path data");
+        }
+        return path;
     }
 
     /**
@@ -701,4 +701,7 @@
             }
         }
     }
+
+    private static native boolean nParseStringForPath(long pathPtr, String pathString,
+            int stringLength);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 66b05a2..461506b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -12968,10 +12968,6 @@
 
             mPrivateFlags |= PFLAG_DIRTY;
 
-            // Release any resources in-case we don't end up drawing again
-            // as anything cached is no longer valid
-            resetDisplayList();
-
             if (invalidateCache) {
                 mPrivateFlags |= PFLAG_INVALIDATED;
                 mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index d80c6a3..fdea1ba 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1625,7 +1625,7 @@
                 if (mPendingConfiguration.seq != 0) {
                     if (DEBUG_CONFIGURATION) Log.v(TAG, "Visible with new config: "
                             + mPendingConfiguration);
-                    updateConfiguration(mPendingConfiguration, !mFirst);
+                    updateConfiguration(new Configuration(mPendingConfiguration), !mFirst);
                     mPendingConfiguration.seq = 0;
                 }
 
@@ -1638,6 +1638,8 @@
                 final boolean stableInsetsChanged = !mPendingStableInsets.equals(
                         mAttachInfo.mStableInsets);
                 final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
+                final boolean surfaceSizeChanged = (relayoutResult
+                        & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0;
                 if (contentInsetsChanged) {
                     mAttachInfo.mContentInsets.set(mPendingContentInsets);
                     if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
@@ -1723,10 +1725,20 @@
                             mAttachInfo.mHardwareRenderer.isEnabled()) {
                         mAttachInfo.mHardwareRenderer.destroy();
                     }
-                } else if (surfaceGenerationId != mSurface.getGenerationId() &&
-                        mSurfaceHolder == null && mAttachInfo.mHardwareRenderer != null) {
+                } else if ((surfaceGenerationId != mSurface.getGenerationId()
+                        || surfaceSizeChanged)
+                        && mSurfaceHolder == null
+                        && mAttachInfo.mHardwareRenderer != null) {
                     mFullRedrawNeeded = true;
                     try {
+                        // Need to do updateSurface (which leads to CanvasContext::setSurface and
+                        // re-create the EGLSurface) if either the Surface changed (as indicated by
+                        // generation id), or WindowManager changed the surface size. The latter is
+                        // because on some chips, changing the consumer side's BufferQueue size may
+                        // not take effect immediately unless we create a new EGLSurface.
+                        // Note that frame size change doesn't always imply surface size change (eg.
+                        // drag resizing uses fullscreen surface), need to check surfaceSizeChanged
+                        // flag from WindowManager.
                         mAttachInfo.mHardwareRenderer.updateSurface(mSurface);
                     } catch (OutOfResourcesException e) {
                         handleOutOfResourcesException(e);
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 43d643e..c08e1b5 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -77,6 +77,11 @@
     public static final int RELAYOUT_RES_DRAG_RESIZING = 0x8;
 
     /**
+     * The window manager has changed the size of the surface from the last call.
+     */
+    public static final int RELAYOUT_RES_SURFACE_RESIZED = 0x10;
+
+    /**
      * Flag for relayout: the client will be later giving
      * internal insets; as a result, the window will not impact other window
      * layouts until the insets are given.
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2fabe33..2983053 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2705,11 +2705,22 @@
             mIsShowingUp = false;
         }
 
-        private class SuggestionInfo {
+        private final class SuggestionInfo {
             int suggestionStart, suggestionEnd; // range of actual suggestion within text
-            SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents
+
+            // the SuggestionSpan that this TextView represents
+            @Nullable
+            SuggestionSpan suggestionSpan;
+
             int suggestionIndex; // the index of this suggestion inside suggestionSpan
-            SpannableStringBuilder text = new SpannableStringBuilder();
+
+            @Nullable
+            final SpannableStringBuilder text = new SpannableStringBuilder();
+
+            void clear() {
+                suggestionSpan = null;
+                text.clear();
+            }
         }
 
         private class SuggestionAdapter extends BaseAdapter {
@@ -2863,9 +2874,11 @@
             return Math.min(positionY, displayMetrics.heightPixels - height);
         }
 
-        @Override
-        public void hide() {
-            super.hide();
+        private void hideWithCleanUp() {
+            for (final SuggestionInfo info : mSuggestionInfos) {
+                info.clear();
+            }
+            hide();
         }
 
         private boolean updateSuggestions() {
@@ -3017,7 +3030,7 @@
                     }
                     mTextView.deleteText_internal(spanUnionStart, spanUnionEnd);
                 }
-                hide();
+                hideWithCleanUp();
                 return;
             }
 
@@ -3025,7 +3038,7 @@
             final int spanEnd = editable.getSpanEnd(suggestionInfo.suggestionSpan);
             if (spanStart < 0 || spanEnd <= spanStart) {
                 // Span has been removed
-                hide();
+                hideWithCleanUp();
                 return;
             }
 
@@ -3100,7 +3113,7 @@
                 mTextView.setCursorPosition_internal(newCursorPosition, newCursorPosition);
             }
 
-            hide();
+            hideWithCleanUp();
         }
     }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index c54a574..b5d994d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3975,6 +3975,15 @@
         }
 
         ((Editable) mText).append(text, start, end);
+
+        if (mAutoLinkMask != 0) {
+            boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
+            // Do not change the movement method for text that support text selection as it
+            // would prevent an arbitrary cursor displacement.
+            if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
+                setMovementMethod(LinkMovementMethod.getInstance());
+            }
+        }
     }
 
     private void updateTextColors() {
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index f190d8c..e8970bc 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -144,6 +144,13 @@
     }
 
     /**
+     * Checks if given array is null or has zero elements.
+     */
+    public static boolean isEmpty(byte[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /**
      * Checks that value is present as at least one of the elements of the array.
      * @param array the array to check in
      * @param value the value to check for
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 60ef4a4..889c7b3 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -126,6 +126,8 @@
     private static final String LOCK_SCREEN_OWNER_INFO_ENABLED =
             Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
 
+    private static final String LOCK_SCREEN_DEVICE_OWNER_INFO = "lockscreen.device_owner_info";
+
     private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents";
 
     // Maximum allowed number of repeated or ordered characters in a sequence before we'll
@@ -578,6 +580,29 @@
     }
 
     /**
+     * Sets the device owner information. If the information is {@code null} or empty then the
+     * device owner info is cleared.
+     *
+     * @param info Device owner information which will be displayed instead of the user
+     * owner info.
+     */
+    public void setDeviceOwnerInfo(String info) {
+        if (info != null && info.isEmpty()) {
+            info = null;
+        }
+
+        setString(LOCK_SCREEN_DEVICE_OWNER_INFO, info, UserHandle.USER_SYSTEM);
+    }
+
+    public String getDeviceOwnerInfo() {
+        return getString(LOCK_SCREEN_DEVICE_OWNER_INFO, UserHandle.USER_SYSTEM);
+    }
+
+    public boolean isDeviceOwnerInfoEnabled() {
+        return getDeviceOwnerInfo() != null;
+    }
+
+    /**
      * Compute the password quality from the given password string.
      */
     static public int computePasswordQuality(String password) {
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index ecee2b2..4d648ce 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -89,6 +89,7 @@
     android_util_Binder.cpp \
     android_util_EventLog.cpp \
     android_util_Log.cpp \
+    android_util_PathParser.cpp \
     android_util_Process.cpp \
     android_util_StringBlock.cpp \
     android_util_XmlBlock.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 4e93730..815d330 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -110,6 +110,7 @@
 extern int register_android_content_AssetManager(JNIEnv* env);
 extern int register_android_util_EventLog(JNIEnv* env);
 extern int register_android_util_Log(JNIEnv* env);
+extern int register_android_util_PathParser(JNIEnv* env);
 extern int register_android_content_StringBlock(JNIEnv* env);
 extern int register_android_content_XmlBlock(JNIEnv* env);
 extern int register_android_graphics_Canvas(JNIEnv* env);
@@ -1300,6 +1301,7 @@
     REG_JNI(register_android_os_SystemClock),
     REG_JNI(register_android_util_EventLog),
     REG_JNI(register_android_util_Log),
+    REG_JNI(register_android_util_PathParser),
     REG_JNI(register_android_content_AssetManager),
     REG_JNI(register_android_content_StringBlock),
     REG_JNI(register_android_content_XmlBlock),
diff --git a/core/jni/android_util_Log.cpp b/core/jni/android_util_Log.cpp
index 2d23cda..c89f293c 100644
--- a/core/jni/android_util_Log.cpp
+++ b/core/jni/android_util_Log.cpp
@@ -42,7 +42,9 @@
 static levels_t levels;
 
 static jboolean isLoggable(const char* tag, jint level) {
-    return __android_log_is_loggable(level, tag, ANDROID_LOG_INFO);
+    return __android_log_is_loggable(level, tag,
+                                     ANDROID_LOG_INFO |
+                                     ANDROID_LOGGABLE_FLAG_NOT_WITHIN_SIGNAL);
 }
 
 static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
diff --git a/core/jni/android_util_PathParser.cpp b/core/jni/android_util_PathParser.cpp
new file mode 100644
index 0000000..bf6ffc54
--- /dev/null
+++ b/core/jni/android_util_PathParser.cpp
@@ -0,0 +1,43 @@
+/*
+ * 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 "jni.h"
+
+#include <PathParser.h>
+#include <SkPath.h>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static bool parseStringForPath(JNIEnv* env, jobject, jlong skPathHandle, jstring inputPathStr,
+        jint strLength) {
+    const char* pathString = env->GetStringUTFChars(inputPathStr, NULL);
+    SkPath* skPath = reinterpret_cast<SkPath*>(skPathHandle);
+    bool hasValidData = android::uirenderer::PathParser::parseStringForSkPath(skPath, pathString,
+            strLength);
+    env->ReleaseStringUTFChars(inputPathStr, pathString);
+    return hasValidData;
+}
+
+static const JNINativeMethod gMethods[] = {
+    {"nParseStringForPath", "(JLjava/lang/String;I)Z", (void*)parseStringForPath}
+};
+
+int register_android_util_PathParser(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, "android/util/PathParser", gMethods, NELEM(gMethods));
+}
+};
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 561bcbc..6bdf71b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1683,6 +1683,10 @@
     <permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS"
         android:protectionLevel="system|signature" />
 
+    <!-- @hide -->
+    <permission android:name="android.permission.STORAGE_INTERNAL"
+        android:protectionLevel="signature" />
+
     <!-- Allows access to ASEC non-destructive API calls
          @hide  -->
     <permission android:name="android.permission.ASEC_ACCESS"
@@ -2702,7 +2706,9 @@
                  android:killAfterRestore="false"
                  android:icon="@drawable/ic_launcher_android"
                  android:supportsRtl="true"
-                 android:theme="@style/Theme.Material.DayNight.DarkActionBar">
+                 android:theme="@style/Theme.Material.DayNight.DarkActionBar"
+                 android:forceDeviceEncrypted="true"
+                 android:encryptionAware="true">
         <activity android:name="com.android.internal.app.ChooserActivity"
                 android:theme="@style/Theme.DeviceDefault.Resolver"
                 android:finishOnCloseSystemDialogs="true"
diff --git a/core/res/res/layout/preference_list_fragment.xml b/core/res/res/layout/preference_list_fragment.xml
index f073c33..fc53a1a 100644
--- a/core/res/res/layout/preference_list_fragment.xml
+++ b/core/res/res/layout/preference_list_fragment.xml
@@ -24,18 +24,23 @@
     android:background="@android:color/transparent"
     android:layout_removeBorders="true">
 
-    <ListView android:id="@android:id/list"
-        style="?attr/preferenceFragmentListStyle"
+    <FrameLayout
+        android:id="@android:id/list_container"
         android:layout_width="match_parent"
         android:layout_height="0px"
-        android:layout_weight="1"
-        android:paddingTop="0dip"
-        android:paddingBottom="@dimen/preference_fragment_padding_bottom"
-        android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
-        android:clipToPadding="false"
-        android:drawSelectorOnTop="false"
-        android:cacheColorHint="@android:color/transparent"
-        android:scrollbarAlwaysDrawVerticalTrack="true" />
+        android:layout_weight="1">
+        <ListView android:id="@android:id/list"
+            style="?attr/preferenceFragmentListStyle"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:paddingTop="0dip"
+            android:paddingBottom="@dimen/preference_fragment_padding_bottom"
+            android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
+            android:clipToPadding="false"
+            android:drawSelectorOnTop="false"
+            android:cacheColorHint="@android:color/transparent"
+            android:scrollbarAlwaysDrawVerticalTrack="true" />
+    </FrameLayout>
 
     <TextView android:id="@android:id/empty"
         android:layout_width="match_parent"
diff --git a/core/res/res/layout/preference_list_fragment_material.xml b/core/res/res/layout/preference_list_fragment_material.xml
index 62bdffd..e411c0e 100644
--- a/core/res/res/layout/preference_list_fragment_material.xml
+++ b/core/res/res/layout/preference_list_fragment_material.xml
@@ -24,18 +24,23 @@
     android:background="@android:color/transparent"
     android:layout_removeBorders="true">
 
-    <ListView android:id="@android:id/list"
-        style="?attr/preferenceFragmentListStyle"
+    <FrameLayout
+        android:id="@android:id/list_container"
         android:layout_width="match_parent"
         android:layout_height="0px"
-        android:layout_weight="1"
-        android:paddingTop="0dip"
-        android:paddingBottom="@dimen/preference_fragment_padding_bottom"
-        android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
-        android:clipToPadding="false"
-        android:drawSelectorOnTop="false"
-        android:cacheColorHint="@android:color/transparent"
-        android:scrollbarAlwaysDrawVerticalTrack="true" />
+        android:layout_weight="1">
+        <ListView android:id="@android:id/list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:paddingTop="0dip"
+            android:paddingBottom="@dimen/preference_fragment_padding_bottom"
+            style="?attr/preferenceFragmentListStyle"
+            android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
+            android:clipToPadding="false"
+            android:drawSelectorOnTop="false"
+            android:cacheColorHint="@android:color/transparent"
+            android:scrollbarAlwaysDrawVerticalTrack="true" />
+    </FrameLayout>
 
     <TextView android:id="@android:id/empty"
         android:layout_width="match_parent"
diff --git a/core/res/res/layout/preference_widget_switch.xml b/core/res/res/layout/preference_widget_switch.xml
index 25e8aa6..80c572b 100644
--- a/core/res/res/layout/preference_widget_switch.xml
+++ b/core/res/res/layout/preference_widget_switch.xml
@@ -17,7 +17,7 @@
 <!-- Layout used by SwitchPreference for the switch widget style. This is inflated
      inside android.R.layout.preference. -->
 <Switch xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+android:id/switchWidget"
+    android:id="@+android:id/switch_widget"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:focusable="false"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d7dd3ec..50cf302 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7714,6 +7714,7 @@
     <declare-styleable name="PreferenceFragment">
         <!-- The layout for the PreferenceFragment. This should rarely need to be changed. -->
         <attr name="layout" />
+        <attr name="divider" />
     </declare-styleable>
 
     <!-- Base attributes available to PreferenceActivity. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 184f2ab..e376903 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1283,6 +1283,7 @@
         <attr name="multiArch" />
         <attr name="extractNativeLibs" />
         <attr name="forceDeviceEncrypted" format="boolean" />
+        <attr name="encryptionAware" />
     </declare-styleable>
     <!-- The <code>permission</code> tag declares a security permission that can be
          used to control access from other packages to specific components or
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e74414a..b87d9e2 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1530,8 +1530,8 @@
     <!-- If the time difference is greater than this threshold in milliseconds,
          then update the time. -->
     <integer name="config_ntpThreshold">5000</integer>
-    <!-- Timeout to wait for NTP server response. -->
-    <integer name="config_ntpTimeout">20000</integer>
+    <!-- Timeout to wait for NTP server response in milliseconds. -->
+    <integer name="config_ntpTimeout">5000</integer>
 
     <!-- Default network policy warning threshold, in megabytes. -->
     <integer name="config_networkPolicyDefaultWarning">2048</integer>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 695dafa..9d5e5ac 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -23,12 +23,14 @@
   <item type="id" name="empty" />
   <item type="id" name="hint" />
   <item type="id" name="icon" />
+  <item type="id" name="icon_frame" />
   <item type="id" name="icon_badge" />
   <item type="id" name="icon1" />
   <item type="id" name="icon2" />
   <item type="id" name="input" />
   <item type="id" name="left_icon" />
   <item type="id" name="list" />
+  <item type="id" name="list_container" />
   <item type="id" name="menu" />
   <item type="id" name="message" />
   <item type="id" name="primary" />
@@ -48,6 +50,7 @@
   <item type="id" name="lock_screen" />
   <item type="id" name="edit" />
   <item type="id" name="widget_frame" />
+  <item type="id" name="switch_widget" />
   <item type="id" name="button1" />
   <item type="id" name="button2" />
   <item type="id" name="button3" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 037f1c4..54e43c8 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2679,6 +2679,9 @@
     <public type="attr" name="contextPopupMenuStyle" />
     <public type="attr" name="textAppearancePopupMenuHeader" />
     <public type="attr" name="windowBackgroundFallback" />
+    <public type="attr" name="forceDeviceEncrypted" />
+    <public type="attr" name="encryptionAware" />
+    <public type="attr" name="preferenceFragmentStyle" />
 
     <public type="style" name="Theme.Material.DayNight" />
     <public type="style" name="Theme.Material.DayNight.DarkActionBar" />
@@ -2699,5 +2702,8 @@
     <public type="style" name="Theme.Material.DayNight.DialogWhenLarge.DarkActionBar" />
 
     <public type="id" name="accessibilityActionSetProgress" />
+    <public type="id" name="icon_frame" />
+    <public type="id" name="list_container" />
+    <public type="id" name="switch_widget" />
 
 </resources>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 58640eb..4b2a451 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -40,6 +40,7 @@
         <item name="layout">@layout/preference_list_fragment_material</item>
         <item name="paddingStart">@dimen/preference_fragment_padding_side_material</item>
         <item name="paddingEnd">@dimen/preference_fragment_padding_side_material</item>
+        <item name="divider">?attr/listDivider</item>
     </style>
 
     <style name="PreferenceActivity.Material">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cda7faa..6820c25 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -162,7 +162,7 @@
   <java-symbol type="id" name="submit_area" />
   <java-symbol type="id" name="switch_new" />
   <java-symbol type="id" name="switch_old" />
-  <java-symbol type="id" name="switchWidget" />
+  <java-symbol type="id" name="switch_widget" />
   <java-symbol type="id" name="text" />
   <java-symbol type="id" name="time" />
   <java-symbol type="id" name="time_current" />
diff --git a/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java b/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
index 4c9b00a..dcf2c89 100644
--- a/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
+++ b/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
@@ -33,7 +33,7 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        mAppsQueryHelper = new AppsQueryHelperTestable(getContext());
+        mAppsQueryHelper = new AppsQueryHelperTestable();
     }
 
     public void testQueryAppsSystemAppsOnly() {
@@ -78,33 +78,20 @@
         assertEqualsIgnoreOrder(Arrays.asList("sys_app1", "sys_app3"), apps);
     }
 
-    public void testQueryAppsDefaultIme() {
-        // Test query default system IMEs
-        List<String> apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES,
+    public void testQueryAppsImes() {
+        // Test query system IMEs
+        List<String> apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_IMES,
                 true, UserHandle.of(UserHandle.myUserId()));
         assertEqualsIgnoreOrder(Arrays.asList("sys_app1"), apps);
 
-        // Test query default IMEs
-        apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES, false,
+        // Test query IMEs
+        apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_IMES, false,
                 UserHandle.of(UserHandle.myUserId()));
         assertEqualsIgnoreOrder(Arrays.asList("sys_app1", "app4"), apps);
-
-        // Test that GET_DEFAULT_IMES cannot be used with a user id different from current process
-        try {
-            mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES, false,
-                    UserHandle.of(UserHandle.USER_NULL));
-            fail("queryApps must fail if wrong user was passed");
-        } catch (IllegalArgumentException e) {
-            // OK
-        }
     }
 
     private class AppsQueryHelperTestable extends AppsQueryHelper {
 
-        public AppsQueryHelperTestable(Context context) {
-            super(context);
-        }
-
         @Override
         protected List<ApplicationInfo> getAllApps(int userId) {
             final ApplicationInfo ai1 = new ApplicationInfo();
@@ -145,18 +132,19 @@
         }
 
         @Override
-        protected List<InputMethodInfo> getInputMethodList() {
+        protected List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int userId) {
             final ResolveInfo sysApp1 = new ResolveInfo();
             sysApp1.serviceInfo = new ServiceInfo();
             sysApp1.serviceInfo.packageName = "sys_app1";
             sysApp1.serviceInfo.name = "name";
-            InputMethodInfo imi1 = new InputMethodInfo(sysApp1, false, null, null, 0, true);
+            sysApp1.serviceInfo.applicationInfo = new ApplicationInfo();
+            sysApp1.serviceInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
             final ResolveInfo app4 = new ResolveInfo();
             app4.serviceInfo = new ServiceInfo();
             app4.serviceInfo.packageName = "app4";
             app4.serviceInfo.name = "name";
-            InputMethodInfo imi2 = new InputMethodInfo(app4, false, null, null, 0, true);
-            return Arrays.asList(imi1, imi2);
+            app4.serviceInfo.applicationInfo = new ApplicationInfo();
+            return Arrays.asList(sysApp1, app4);
         }
     }
 
diff --git a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
index 87b3785..3ce45e9 100644
--- a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
+++ b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
@@ -61,11 +61,13 @@
         final int expectedHighlightTextColor = tmpTp.getColor();
         final float expectedHighlightTextSize = tmpTp.getTextSize();
 
-        // Create and wait until SuggestionsPopupWindow is shown.
         final EditText editText = (EditText) activity.findViewById(R.id.textview);
         final Editor editor = editText.getEditorForTesting();
         assertNotNull(editor);
-        activity.runOnUiThread(new Runnable() {
+
+        // Request to show SuggestionsPopupWindow.
+        Runnable showSuggestionWindowRunner = new Runnable() {
+            @Override
             public void run() {
                 SpannableStringBuilder ssb = new SpannableStringBuilder();
                 ssb.append(sampleText);
@@ -78,8 +80,7 @@
                 Selection.setSelection(editText.getText(), singleWordSpanStart, singleWordSpanEnd);
                 editText.onTextContextMenuItem(TextView.ID_REPLACE);
             }
-        });
-        getInstrumentation().waitForIdleSync();
+        };
 
         // In this test, the SuggestionsPopupWindow looks like
         //   abc def ghi
@@ -91,7 +92,8 @@
         // | DELETE        |
         // -----------------
         // *XX* means that XX is highlighted.
-        activity.runOnUiThread(new Runnable() {
+        Runnable popupVaridator = new Runnable() {
+            @Override
             public void run() {
                 Editor.SuggestionsPopupWindow popupWindow =
                         editor.getSuggestionsPopupWindowForTesting();
@@ -155,6 +157,25 @@
                     assertEquals(multiWordSpanEnd, spanned.getSpanEnd(taSpan[0]));
                 }
             }
+        };
+
+        // Show the SuggestionWindow and verify the contents.
+        activity.runOnUiThread(showSuggestionWindowRunner);
+        getInstrumentation().waitForIdleSync();
+        activity.runOnUiThread(popupVaridator);
+
+        // Request to hide the SuggestionPopupWindow and wait until it is hidden.
+        activity.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                editText.setText("");
+            }
         });
+        getInstrumentation().waitForIdleSync();
+
+        // Show and verify the contents again.
+        activity.runOnUiThread(showSuggestionWindowRunner);
+        getInstrumentation().waitForIdleSync();
+        activity.runOnUiThread(popupVaridator);
     }
 }
diff --git a/docs/html/guide/topics/manifest/uses-feature-element.jd b/docs/html/guide/topics/manifest/uses-feature-element.jd
index c1ccef0..e22dc4a 100644
--- a/docs/html/guide/topics/manifest/uses-feature-element.jd
+++ b/docs/html/guide/topics/manifest/uses-feature-element.jd
@@ -562,10 +562,11 @@
     <tr>
        <td rowspan="6">Camera</td>
        <td><code>android.hardware.camera</code></td>
-       <td>The application uses the device's camera. If the device supports
-           multiple cameras, the application uses the camera that facing
-           away from the screen.</td>
-       <td></td>
+       <td>The application uses the device's back-facing (main) camera.</td>
+       <td>Importantly, devices with only a front-facing camera will not list this
+           feature, so the <code>android.hardware.camera.any</code> feature should be
+           used instead if a camera facing any direction is acceptable for the
+           application.</td>
     </tr>
 <tr>
   <td><code>android.hardware.camera.autofocus</code></td>
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 39d13df..64f2698 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -913,16 +913,26 @@
     protected void onBoundsChange(Rect bounds) {}
 
     /**
-     * Return the intrinsic width of the underlying drawable object.  Returns
-     * -1 if it has no intrinsic width, such as with a solid color.
+     * Returns the drawable's intrinsic width.
+     * <p>
+     * Intrinsic width is the width at which the drawable would like to be laid
+     * out, including any inherent padding. If the drawable has no intrinsic
+     * width, such as a solid color, this method returns -1.
+     *
+     * @return the intrinsic width, or -1 if no intrinsic width
      */
     public int getIntrinsicWidth() {
         return -1;
     }
 
     /**
-     * Return the intrinsic height of the underlying drawable object. Returns
-     * -1 if it has no intrinsic height, such as with a solid color.
+     * Returns the drawable's intrinsic height.
+     * <p>
+     * Intrinsic height is the height at which the drawable would like to be
+     * laid out, including any inherent padding. If the drawable has no
+     * intrinsic height, such as a solid color, this method returns -1.
+     *
+     * @return the intrinsic height, or -1 if no intrinsic height
      */
     public int getIntrinsicHeight() {
         return -1;
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index 927b9c9..36d4272 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -222,12 +222,20 @@
 
     @Override
     public int getIntrinsicWidth() {
-        return getDrawable().getIntrinsicWidth() + mState.mInsetLeft + mState.mInsetRight;
+        final int childWidth = getDrawable().getIntrinsicWidth();
+        if (childWidth < 0) {
+            return -1;
+        }
+        return childWidth + mState.mInsetLeft + mState.mInsetRight;
     }
 
     @Override
     public int getIntrinsicHeight() {
-        return getDrawable().getIntrinsicHeight() + mState.mInsetTop + mState.mInsetBottom;
+        final int childHeight = getDrawable().getIntrinsicHeight();
+        if (childHeight < 0) {
+            return -1;
+        }
+        return childHeight + mState.mInsetTop + mState.mInsetBottom;
     }
 
     @Override
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 5cdd723..4acad67 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -9,6 +9,7 @@
     font/Font.cpp \
     renderstate/Blend.cpp \
     renderstate/MeshState.cpp \
+    renderstate/OffscreenBufferPool.cpp \
     renderstate/PixelBufferState.cpp \
     renderstate/RenderState.cpp \
     renderstate/Scissor.cpp \
@@ -216,6 +217,7 @@
     unit_tests/LayerUpdateQueueTests.cpp \
     unit_tests/LinearAllocatorTests.cpp \
     unit_tests/PathParserTests.cpp \
+    unit_tests/OffscreenBufferPoolTests.cpp \
     unit_tests/StringUtilsTests.cpp
 
 ifeq (true, $(HWUI_NEW_OPS))
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 2fca5ea..1aa291f 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -19,104 +19,29 @@
 #include "Caches.h"
 #include "Glop.h"
 #include "GlopBuilder.h"
+#include "renderstate/OffscreenBufferPool.h"
 #include "renderstate/RenderState.h"
-#include "utils/FatVector.h"
 #include "utils/GLUtils.h"
+#include "VertexBuffer.h"
 
 namespace android {
 namespace uirenderer {
 
 ////////////////////////////////////////////////////////////////////////////////
-// OffscreenBuffer
-////////////////////////////////////////////////////////////////////////////////
-
-OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
-        uint32_t textureWidth, uint32_t textureHeight,
-        uint32_t viewportWidth, uint32_t viewportHeight)
-        : renderState(renderState)
-        , viewportWidth(viewportWidth)
-        , viewportHeight(viewportHeight)
-        , texture(caches) {
-    texture.width = textureWidth;
-    texture.height = textureHeight;
-
-    caches.textureState().activateTexture(0);
-    glGenTextures(1, &texture.id);
-    caches.textureState().bindTexture(GL_TEXTURE_2D, texture.id);
-
-    texture.setWrap(GL_CLAMP_TO_EDGE, false, false, GL_TEXTURE_2D);
-    // not setting filter on texture, since it's set when drawing, based on transform
-
-    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0,
-            GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
-}
-
-void OffscreenBuffer::updateMeshFromRegion() {
-    // avoid T-junctions as they cause artifacts in between the resultant
-    // geometry when complex transforms occur.
-    // TODO: generate the safeRegion only if necessary based on drawing transform
-    Region safeRegion = Region::createTJunctionFreeRegion(region);
-
-    size_t count;
-    const android::Rect* rects = safeRegion.getArray(&count);
-
-    const float texX = 1.0f / float(viewportWidth);
-    const float texY = 1.0f / float(viewportHeight);
-
-    FatVector<TextureVertex, 64> meshVector(count * 4); // uses heap if more than 64 vertices needed
-    TextureVertex* mesh = &meshVector[0];
-    for (size_t i = 0; i < count; i++) {
-        const android::Rect* r = &rects[i];
-
-        const float u1 = r->left * texX;
-        const float v1 = (viewportHeight - r->top) * texY;
-        const float u2 = r->right * texX;
-        const float v2 = (viewportHeight - r->bottom) * texY;
-
-        TextureVertex::set(mesh++, r->left, r->top, u1, v1);
-        TextureVertex::set(mesh++, r->right, r->top, u2, v1);
-        TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
-        TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
-    }
-    elementCount = count * 6;
-    renderState.meshState().genOrUpdateMeshBuffer(&vbo,
-            sizeof(TextureVertex) * count * 4,
-            &meshVector[0],
-            GL_DYNAMIC_DRAW); // TODO: GL_STATIC_DRAW if savelayer
-}
-
-OffscreenBuffer::~OffscreenBuffer() {
-    texture.deleteTexture();
-    renderState.meshState().deleteMeshBuffer(vbo);
-    elementCount = 0;
-    vbo = 0;
-}
-
-////////////////////////////////////////////////////////////////////////////////
 // BakedOpRenderer
 ////////////////////////////////////////////////////////////////////////////////
 
-OffscreenBuffer* BakedOpRenderer::createOffscreenBuffer(RenderState& renderState,
-        uint32_t width, uint32_t height) {
-    // TODO: get from cache!
-    return new OffscreenBuffer(renderState, Caches::getInstance(), width, height, width, height);
-}
-
-void BakedOpRenderer::destroyOffscreenBuffer(OffscreenBuffer* offscreenBuffer) {
-    // TODO: return texture/offscreenbuffer to cache!
-    delete offscreenBuffer;
-}
-
-OffscreenBuffer* BakedOpRenderer::createLayer(uint32_t width, uint32_t height) {
+OffscreenBuffer* BakedOpRenderer::startTemporaryLayer(uint32_t width, uint32_t height) {
     LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
 
-    OffscreenBuffer* buffer = createOffscreenBuffer(mRenderState, width, height);
-    startLayer(buffer);
+    OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height);
+    startRepaintLayer(buffer);
     return buffer;
 }
 
-void BakedOpRenderer::startLayer(OffscreenBuffer* offscreenBuffer) {
+void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer) {
+    LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
+
     mRenderTarget.offscreenBuffer = offscreenBuffer;
 
     // create and bind framebuffer
@@ -261,6 +186,71 @@
     renderer.renderGlop(state, glop);
 }
 
+namespace VertexBufferRenderFlags {
+    enum {
+        Offset = 0x1,
+        ShadowInterp = 0x2,
+    };
+}
+
+static void renderVertexBuffer(BakedOpRenderer& renderer, const BakedOpState& state,
+        const VertexBuffer& vertexBuffer, float translateX, float translateY,
+        SkPaint& paint, int vertexBufferRenderFlags) {
+    if (CC_LIKELY(vertexBuffer.getVertexCount())) {
+        bool shadowInterp = vertexBufferRenderFlags & VertexBufferRenderFlags::ShadowInterp;
+        const int transformFlags = TransformFlags::OffsetByFudgeFactor;
+        Glop glop;
+        GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+                .setRoundRectClipState(state.roundRectClipState)
+                .setMeshVertexBuffer(vertexBuffer, shadowInterp)
+                .setFillPaint(paint, state.alpha)
+                .setTransform(state.computedState.transform, transformFlags)
+                .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds())
+                .build();
+        renderer.renderGlop(state, glop);
+    }
+}
+
+static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, float casterAlpha,
+        const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer) {
+    SkPaint paint;
+    paint.setAntiAlias(true); // want to use AlphaVertex
+
+    // The caller has made sure casterAlpha > 0.
+    uint8_t ambientShadowAlpha = 128u; //TODO: mAmbientShadowAlpha;
+    if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) {
+        ambientShadowAlpha = Properties::overrideAmbientShadowStrength;
+    }
+    if (ambientShadowVertexBuffer && ambientShadowAlpha > 0) {
+        paint.setAlpha((uint8_t)(casterAlpha * ambientShadowAlpha));
+        renderVertexBuffer(renderer, state, *ambientShadowVertexBuffer, 0, 0,
+                paint, VertexBufferRenderFlags::ShadowInterp);
+    }
+
+    uint8_t spotShadowAlpha = 128u; //TODO: mSpotShadowAlpha;
+    if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) {
+        spotShadowAlpha = Properties::overrideSpotShadowStrength;
+    }
+    if (spotShadowVertexBuffer && spotShadowAlpha > 0) {
+        paint.setAlpha((uint8_t)(casterAlpha * spotShadowAlpha));
+        renderVertexBuffer(renderer, state, *spotShadowVertexBuffer, 0, 0,
+                paint, VertexBufferRenderFlags::ShadowInterp);
+    }
+}
+
+void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) {
+    TessellationCache::vertexBuffer_pair_t buffers;
+    Vector3 lightCenter = { 300, 300, 300 }; // TODO!
+    float lightRadius = 150; // TODO!
+
+    renderer.caches().tessellationCache.getShadowBuffers(&state.computedState.transform,
+            op.localClipRect, op.casterAlpha >= 1.0f, op.casterPath,
+            &op.shadowMatrixXY, &op.shadowMatrixZ, lightCenter, lightRadius,
+            buffers);
+
+    renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
+}
+
 void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, const BakedOpState& state) {
     Glop glop;
     GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
@@ -291,7 +281,7 @@
     renderer.renderGlop(state, glop);
 
     if (op.destroy) {
-        BakedOpRenderer::destroyOffscreenBuffer(buffer);
+        renderer.renderState().layerPool().putOrDelete(buffer);
     }
 }
 
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index aa1e67d..d6d9cb1 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -29,33 +29,6 @@
 class RenderState;
 
 /**
- * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
- * encompasses enough information to draw it back on screen (minus paint properties, which are held
- * by LayerOp).
- */
-class OffscreenBuffer {
-public:
-    OffscreenBuffer(RenderState& renderState, Caches& caches,
-            uint32_t textureWidth, uint32_t textureHeight,
-            uint32_t viewportWidth, uint32_t viewportHeight);
-    ~OffscreenBuffer();
-
-    // must be called prior to rendering, to construct/update vertex buffer
-    void updateMeshFromRegion();
-
-    RenderState& renderState;
-    uint32_t viewportWidth;
-    uint32_t viewportHeight;
-    Texture texture;
-
-    // Portion of offscreen buffer that has been drawn to. Used to minimize drawing area when
-    // drawing back to screen / parent FBO.
-    Region region;
-    GLsizei elementCount = 0;
-    GLuint vbo = 0;
-};
-
-/**
  * Main rendering manager for a collection of work - one frame + any contained FBOs.
  *
  * Manages frame and FBO lifecycle, binding the GL framebuffer as appropriate. This is the only
@@ -72,17 +45,13 @@
             , mOpaque(opaque) {
     }
 
-    static OffscreenBuffer* createOffscreenBuffer(RenderState& renderState,
-            uint32_t width, uint32_t height);
-    static void destroyOffscreenBuffer(OffscreenBuffer*);
-
     RenderState& renderState() { return mRenderState; }
     Caches& caches() { return mCaches; }
 
     void startFrame(uint32_t width, uint32_t height);
     void endFrame();
-    OffscreenBuffer* createLayer(uint32_t width, uint32_t height);
-    void startLayer(OffscreenBuffer* offscreenBuffer);
+    OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height);
+    void startRepaintLayer(OffscreenBuffer* offscreenBuffer);
     void endLayer();
 
     Texture* getTexture(const SkBitmap* bitmap);
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index ddb8c84..9a40c3b 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -89,6 +89,21 @@
          *         and early return null in one place.
          */
     }
+
+    /**
+     * Constructor for unbounded ops without transform/clip (namely shadows)
+     *
+     * Since the op doesn't have known bounds, we conservatively set the mapped bounds
+     * to the current clipRect, and clipSideFlags to Full.
+     */
+    ResolvedRenderState(const Snapshot& snapshot) {
+        transform = *snapshot.transform;
+        clipRect = snapshot.getRenderTargetClip();
+        clippedBounds = clipRect;
+        transform.mapRect(clippedBounds);
+        clipSideFlags = OpClipSideFlags::Full;
+    }
+
     Matrix4 transform;
     Rect clipRect;
     int clipSideFlags = 0;
@@ -104,8 +119,7 @@
 public:
     static BakedOpState* tryConstruct(LinearAllocator& allocator,
             const Snapshot& snapshot, const RecordedOp& recordedOp) {
-        BakedOpState* bakedOp = new (allocator) BakedOpState(
-                snapshot, recordedOp);
+        BakedOpState* bakedOp = new (allocator) BakedOpState(snapshot, recordedOp);
         if (bakedOp->computedState.clippedBounds.isEmpty()) {
             // bounds are empty, so op is rejected
             allocator.rewindIfLastAlloc(bakedOp);
@@ -114,6 +128,14 @@
         return bakedOp;
     }
 
+    static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
+            const Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
+        if (snapshot.getRenderTargetClip().isEmpty()) return nullptr;
+
+        // clip isn't empty, so construct the op
+        return new (allocator) BakedOpState(snapshot, shadowOpPtr);
+    }
+
     static void* operator new(size_t size, LinearAllocator& allocator) {
         return allocator.alloc(size);
     }
@@ -134,6 +156,13 @@
             , roundRectClipState(snapshot.roundRectClipState)
             , projectionPathMask(snapshot.projectionPathMask)
             , op(&recordedOp) {}
+
+    BakedOpState(const Snapshot& snapshot, const ShadowOp* shadowOpPtr)
+            : computedState(snapshot)
+            , alpha(snapshot.alpha)
+            , roundRectClipState(snapshot.roundRectClipState)
+            , projectionPathMask(snapshot.projectionPathMask)
+            , op(shadowOpPtr) {}
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index fc08504..609103b 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -17,6 +17,14 @@
 #ifndef ANDROID_HWUI_DISPLAY_LIST_RENDERER_H
 #define ANDROID_HWUI_DISPLAY_LIST_RENDERER_H
 
+#include "Canvas.h"
+#include "CanvasState.h"
+#include "DisplayList.h"
+#include "RenderNode.h"
+#include "ResourceCache.h"
+#include "SkiaCanvasProxy.h"
+#include "utils/Macros.h"
+
 #include <SkDrawFilter.h>
 #include <SkMatrix.h>
 #include <SkPaint.h>
@@ -25,13 +33,6 @@
 #include <SkTLazy.h>
 #include <cutils/compiler.h>
 
-#include "Canvas.h"
-#include "CanvasState.h"
-#include "DisplayList.h"
-#include "SkiaCanvasProxy.h"
-#include "RenderNode.h"
-#include "ResourceCache.h"
-
 namespace android {
 namespace uirenderer {
 
@@ -66,7 +67,7 @@
     virtual ~DisplayListCanvas();
 
     void reset(int width, int height);
-    __attribute__((warn_unused_result)) DisplayList* finishRecording();
+    WARN_UNUSED_RESULT DisplayList* finishRecording();
 
 // ----------------------------------------------------------------------------
 // HWUI Canvas state operations
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 39cadd1..b117754 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-#include <GLES2/gl2.h>
+#include "LayerCache.h"
+
+#include "Caches.h"
+#include "Properties.h"
 
 #include <utils/Log.h>
 
-#include "Caches.h"
-#include "LayerCache.h"
-#include "Properties.h"
+#include <GLES2/gl2.h>
 
 namespace android {
 namespace uirenderer {
@@ -29,15 +30,9 @@
 // Constructors/destructor
 ///////////////////////////////////////////////////////////////////////////////
 
-LayerCache::LayerCache(): mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
-    char property[PROPERTY_VALUE_MAX];
-    if (property_get(PROPERTY_LAYER_CACHE_SIZE, property, nullptr) > 0) {
-        INIT_LOGD("  Setting layer cache size to %sMB", property);
-        setMaxSize(MB(atof(property)));
-    } else {
-        INIT_LOGD("  Using default layer cache size of %.2fMB", DEFAULT_LAYER_CACHE_SIZE);
-    }
-}
+LayerCache::LayerCache()
+        : mSize(0)
+        , mMaxSize(Properties::layerPoolSize) {}
 
 LayerCache::~LayerCache() {
     clear();
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 68f80ea..80efaed 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -22,6 +22,7 @@
 #include "utils/PaintUtils.h"
 
 #include <SkCanvas.h>
+#include <SkPathOps.h>
 #include <utils/Trace.h>
 #include <utils/TypeHelpers.h>
 
@@ -312,7 +313,7 @@
         : mCanvasState(*this) {
     ATRACE_NAME("prepare drawing commands");
     mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
-        mLayerStack.push_back(0);
+    mLayerStack.push_back(0);
 
     mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
             clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
@@ -347,7 +348,6 @@
 OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList)
         : mCanvasState(*this) {
     ATRACE_NAME("prepare drawing commands");
-
     mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
     mLayerStack.push_back(0);
 
@@ -462,8 +462,60 @@
 }
 
 void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
-    // TODO
+    auto& node = *casterNodeOp.renderNode;
+    auto& properties = node.properties();
+
+    if (properties.getAlpha() <= 0.0f
+            || properties.getOutline().getAlpha() <= 0.0f
+            || !properties.getOutline().getPath()
+            || properties.getScaleX() == 0
+            || properties.getScaleY() == 0) {
+        // no shadow to draw
+        return;
+    }
+
+    const SkPath* casterOutlinePath = properties.getOutline().getPath();
+    const SkPath* revealClipPath = properties.getRevealClip().getPath();
+    if (revealClipPath && revealClipPath->isEmpty()) return;
+
+    float casterAlpha = properties.getAlpha() * properties.getOutline().getAlpha();
+
+    // holds temporary SkPath to store the result of intersections
+    SkPath* frameAllocatedPath = nullptr;
+    const SkPath* casterPath = casterOutlinePath;
+
+    // intersect the shadow-casting path with the reveal, if present
+    if (revealClipPath) {
+        frameAllocatedPath = createFrameAllocatedPath();
+
+        Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, frameAllocatedPath);
+        casterPath = frameAllocatedPath;
+    }
+
+    // intersect the shadow-casting path with the clipBounds, if present
+    if (properties.getClippingFlags() & CLIP_TO_CLIP_BOUNDS) {
+        if (!frameAllocatedPath) {
+            frameAllocatedPath = createFrameAllocatedPath();
+        }
+        Rect clipBounds;
+        properties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds);
+        SkPath clipBoundsPath;
+        clipBoundsPath.addRect(clipBounds.left, clipBounds.top,
+                clipBounds.right, clipBounds.bottom);
+
+        Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, frameAllocatedPath);
+        casterPath = frameAllocatedPath;
+    }
+
+    ShadowOp* shadowOp = new (mAllocator) ShadowOp(casterNodeOp, casterAlpha, casterPath,
+            mCanvasState.getLocalClipBounds());
+    BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
+            mAllocator, *mCanvasState.currentSnapshot(), shadowOp);
+    if (CC_LIKELY(bakedOpState)) {
+        currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow);
+    }
 }
+
 /**
  * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
  *
@@ -593,5 +645,9 @@
     LOG_ALWAYS_FATAL("unsupported");
 }
 
+void OpReorderer::onShadowOp(const ShadowOp& op) {
+    LOG_ALWAYS_FATAL("unsupported");
+}
+
 } // namespace uirenderer
 } // namespace android
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 936b6ed..2c30f0d 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -51,6 +51,7 @@
         AlphaMaskTexture,
         Text,
         ColorText,
+        Shadow,
 
         Count // must be last
     };
@@ -152,11 +153,11 @@
             LayerReorderer& layer = mLayerReorderers[i];
             if (layer.renderNode) {
                 // cached HW layer - can't skip layer if empty
-                renderer.startLayer(layer.offscreenBuffer);
+                renderer.startRepaintLayer(layer.offscreenBuffer);
                 layer.replayBakedOpsImpl((void*)&renderer, receivers);
                 renderer.endLayer();
             } else if (!layer.empty()) { // save layer - skip entire layer if empty
-                layer.offscreenBuffer = renderer.createLayer(layer.width, layer.height);
+                layer.offscreenBuffer = renderer.startTemporaryLayer(layer.width, layer.height);
                 layer.replayBakedOpsImpl((void*)&renderer, receivers);
                 renderer.endLayer();
             }
@@ -210,6 +211,10 @@
 
     void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);
 
+    SkPath* createFrameAllocatedPath() {
+        mFrameAllocatedPaths.emplace_back(new SkPath);
+        return mFrameAllocatedPaths.back().get();
+    }
     /**
      * Declares all OpReorderer::onXXXXOp() methods for every RecordedOp type.
      *
@@ -220,6 +225,8 @@
     void on##Type(const Type& op);
     MAP_OPS(INTERNAL_OP_HANDLER)
 
+    std::vector<std::unique_ptr<SkPath> > mFrameAllocatedPaths;
+
     // List of every deferred layer's render state. Replayed in reverse order to render a frame.
     std::vector<LayerReorderer> mLayerReorderers;
 
diff --git a/libs/hwui/PathParser.cpp b/libs/hwui/PathParser.cpp
index e8ed8a1..61b9d21 100644
--- a/libs/hwui/PathParser.cpp
+++ b/libs/hwui/PathParser.cpp
@@ -183,10 +183,16 @@
     ALOGD("points are : %s", os.str().c_str());
 }
 
-void PathParser::parseStringForSkPath(SkPath* skPath, const char* pathStr, size_t strLen) {
+bool PathParser::parseStringForSkPath(SkPath* skPath, const char* pathStr, size_t strLen) {
     PathData pathData;
     getPathDataFromString(&pathData, pathStr, strLen);
+
+    // Check if there is valid data coming out of parsing the string.
+    if (pathData.verbs.size() == 0) {
+        return false;
+    }
     VectorDrawablePath::verbsToPath(skPath, &pathData);
+    return true;
 }
 
 }; // namespace uirenderer
diff --git a/libs/hwui/PathParser.h b/libs/hwui/PathParser.h
index 6dc7ee1..d30bb0f 100644
--- a/libs/hwui/PathParser.h
+++ b/libs/hwui/PathParser.h
@@ -21,13 +21,18 @@
 
 #include <jni.h>
 #include <android/log.h>
+#include <cutils/compiler.h>
 
 namespace android {
 namespace uirenderer {
 
 class PathParser {
 public:
-    static void parseStringForSkPath(SkPath* outPath, const char* pathStr, size_t strLength);
+    /**
+     * Parse the string literal and create a Skia Path. Return true on success.
+     */
+    ANDROID_API static bool parseStringForSkPath(SkPath* outPath, const char* pathStr,
+            size_t strLength);
     static void getPathDataFromString(PathData* outData, const char* pathStr, size_t strLength);
     static void dump(const PathData& data);
 };
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index e818186..0669596 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -37,6 +37,7 @@
 bool Properties::enablePartialUpdates = true;
 
 float Properties::textGamma = DEFAULT_TEXT_GAMMA;
+int Properties::layerPoolSize = DEFAULT_LAYER_CACHE_SIZE;
 
 DebugLevel Properties::debugLevel = kDebugDisabled;
 OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default;
@@ -52,10 +53,19 @@
 ProfileType Properties::sProfileType = ProfileType::None;
 bool Properties::sDisableProfileBars = false;
 
+static int property_get_int(const char* key, int defaultValue) {
+    char buf[PROPERTY_VALUE_MAX] = {'\0',};
+
+    if (property_get(key, buf, "") > 0) {
+        return atoi(buf);
+    }
+    return defaultValue;
+}
+
 static float property_get_float(const char* key, float defaultValue) {
     char buf[PROPERTY_VALUE_MAX] = {'\0',};
 
-    if (property_get(PROPERTY_PROFILE, buf, "") > 0) {
+    if (property_get(key, buf, "") > 0) {
         return atof(buf);
     }
     return defaultValue;
@@ -114,16 +124,14 @@
 
     showDirtyRegions = property_get_bool(PROPERTY_DEBUG_SHOW_DIRTY_REGIONS, false);
 
-    debugLevel = kDebugDisabled;
-    if (property_get(PROPERTY_DEBUG, property, nullptr) > 0) {
-        debugLevel = (DebugLevel) atoi(property);
-    }
+    debugLevel = (DebugLevel) property_get_int(PROPERTY_DEBUG, kDebugDisabled);
 
     skipEmptyFrames = property_get_bool(PROPERTY_SKIP_EMPTY_DAMAGE, true);
     useBufferAge = property_get_bool(PROPERTY_USE_BUFFER_AGE, true);
     enablePartialUpdates = property_get_bool(PROPERTY_ENABLE_PARTIAL_UPDATES, true);
 
     textGamma = property_get_float(PROPERTY_TEXT_GAMMA, DEFAULT_TEXT_GAMMA);
+    layerPoolSize = MB(property_get_float(PROPERTY_LAYER_CACHE_SIZE, DEFAULT_LAYER_CACHE_SIZE));
 
     return (prevDebugLayersUpdates != debugLayersUpdates)
             || (prevDebugOverdraw != debugOverdraw)
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 1293c78..1dde7e0 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -267,6 +267,8 @@
 
     static float textGamma;
 
+    static int layerPoolSize;
+
     static DebugLevel debugLevel;
     static OverdrawColorSet overdrawColorSet;
     static StencilClipDebug debugStencilClip;
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index 04af8e3..bb7a0a7c 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -43,6 +43,7 @@
         OP_FN(BitmapOp) \
         OP_FN(RectOp) \
         OP_FN(RenderNodeOp) \
+        OP_FN(ShadowOp) \
         OP_FN(SimpleRectsOp) \
         OP_FN(BeginLayerOp) \
         OP_FN(EndLayerOp) \
@@ -109,6 +110,31 @@
             : SUPER(RectOp) {}
 };
 
+/**
+ * Real-time, dynamic-lit shadow.
+ *
+ * Uses invalid/empty bounds and matrix since ShadowOp bounds aren't known at defer time,
+ * and are resolved dynamically, and transform isn't needed.
+ *
+ * State construction handles these properties specially.
+ */
+struct ShadowOp : RecordedOp {
+    ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath, const Rect& clipRect)
+            : RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), clipRect, nullptr)
+            , shadowMatrixXY(casterOp.localMatrix)
+            , shadowMatrixZ(casterOp.localMatrix)
+            , casterAlpha(casterAlpha)
+            , casterPath(casterPath) {
+        const RenderNode& node = *casterOp.renderNode;
+        node.applyViewPropertyTransforms(shadowMatrixXY, false);
+        node.applyViewPropertyTransforms(shadowMatrixZ, true);
+    };
+    Matrix4 shadowMatrixXY;
+    Matrix4 shadowMatrixZ;
+    const float casterAlpha;
+    const SkPath* casterPath;
+};
+
 struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?)
     SimpleRectsOp(BASE_PARAMS, Vertex* vertices, size_t vertexCount)
             : SUPER(SimpleRectsOp)
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index fc84c98..f26b0c8 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -20,11 +20,12 @@
 #include "Canvas.h"
 #include "CanvasState.h"
 #include "DisplayList.h"
-#include "utils/LinearAllocator.h"
-#include "utils/NinePatch.h"
 #include "ResourceCache.h"
 #include "SkiaCanvasProxy.h"
 #include "Snapshot.h"
+#include "utils/LinearAllocator.h"
+#include "utils/Macros.h"
+#include "utils/NinePatch.h"
 
 #include <SkDrawFilter.h>
 #include <SkPaint.h>
@@ -49,7 +50,7 @@
     virtual ~RecordingCanvas();
 
     void reset(int width, int height);
-    __attribute__((warn_unused_result)) DisplayList* finishRecording();
+    WARN_UNUSED_RESULT DisplayList* finishRecording();
 
 // ----------------------------------------------------------------------------
 // MISC HWUI OPERATIONS - TODO: CATEGORIZE
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 15ca718..e177f9a 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -248,22 +248,31 @@
     }
 }
 
-layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) {
+static layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) {
 #if HWUI_NEW_OPS
-    return BakedOpRenderer::createOffscreenBuffer(renderState, width, height);
+    return renderState.layerPool().get(renderState, width, height);
 #else
     return LayerRenderer::createRenderLayer(renderState, width, height);
 #endif
 }
 
-void destroyLayer(layer_t* layer) {
+static void destroyLayer(layer_t* layer) {
 #if HWUI_NEW_OPS
-    BakedOpRenderer::destroyOffscreenBuffer(layer);
+    RenderState& renderState = layer->renderState;
+    renderState.layerPool().putOrDelete(layer);
 #else
     LayerRenderer::destroyLayer(layer);
 #endif
 }
 
+static bool layerMatchesWidthAndHeight(layer_t* layer, int width, int height) {
+#if HWUI_NEW_OPS
+    return layer->viewportWidth == (uint32_t) width && layer->viewportHeight == (uint32_t)height;
+#else
+    return layer->layer.getWidth() == width && layer->layer.getHeight() == height;
+#endif
+}
+
 void RenderNode::pushLayerUpdate(TreeInfo& info) {
     LayerType layerType = properties().effectiveLayerType();
     // If we are not a layer OR we cannot be rendered (eg, view was detached)
@@ -278,17 +287,16 @@
 
     bool transformUpdateNeeded = false;
     if (!mLayer) {
-            mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
-            damageSelf(info);
-            transformUpdateNeeded = true;
+        mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
+        damageSelf(info);
+        transformUpdateNeeded = true;
+    } else if (!layerMatchesWidthAndHeight(mLayer, getWidth(), getHeight())) {
 #if HWUI_NEW_OPS
-    } else if (mLayer->viewportWidth != (uint32_t) getWidth()
-            || mLayer->viewportHeight != (uint32_t)getHeight()) {
-        // TODO: allow node's layer to grow larger
-        if ((uint32_t)getWidth() > mLayer->texture.width
-                || (uint32_t)getHeight() > mLayer->texture.height) {
+        RenderState& renderState = mLayer->renderState;
+        if (properties().fitsOnLayer()) {
+            mLayer = renderState.layerPool().resize(mLayer, getWidth(), getHeight());
+        } else {
 #else
-    } else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) {
         if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) {
 #endif
             destroyLayer(mLayer);
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index ca7789e..0bd5b65 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -608,12 +608,16 @@
                 && getOutline().getAlpha() != 0.0f;
     }
 
-    bool promotedToLayer() const {
+    bool fitsOnLayer() const {
         const DeviceInfo* deviceInfo = DeviceInfo::get();
         LOG_ALWAYS_FATAL_IF(!deviceInfo, "DeviceInfo uninitialized");
+        return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize()
+                        && mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize();
+    }
+
+    bool promotedToLayer() const {
         return mLayerProperties.mType == LayerType::None
-                && mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize()
-                && mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize()
+                && fitsOnLayer()
                 && (mComputedFields.mNeedLayerForFunctors
                         || (!MathUtils::isZero(mPrimitiveFields.mAlpha)
                                 && mPrimitiveFields.mAlpha < 1
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp
new file mode 100644
index 0000000..6b44557
--- /dev/null
+++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp
@@ -0,0 +1,196 @@
+/*
+ * 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 "OffscreenBufferPool.h"
+
+#include "Caches.h"
+#include "Properties.h"
+#include "renderstate/RenderState.h"
+#include "utils/FatVector.h"
+
+#include <utils/Log.h>
+
+#include <GLES2/gl2.h>
+
+namespace android {
+namespace uirenderer {
+
+////////////////////////////////////////////////////////////////////////////////
+// OffscreenBuffer
+////////////////////////////////////////////////////////////////////////////////
+
+OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
+        uint32_t viewportWidth, uint32_t viewportHeight)
+        : renderState(renderState)
+        , viewportWidth(viewportWidth)
+        , viewportHeight(viewportHeight)
+        , texture(caches) {
+    texture.width = computeIdealDimension(viewportWidth);
+    texture.height = computeIdealDimension(viewportHeight);
+    texture.blend = true;
+
+    caches.textureState().activateTexture(0);
+    glGenTextures(1, &texture.id);
+    caches.textureState().bindTexture(GL_TEXTURE_2D, texture.id);
+
+    texture.setWrap(GL_CLAMP_TO_EDGE, false, false, GL_TEXTURE_2D);
+    // not setting filter on texture, since it's set when drawing, based on transform
+
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0,
+            GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+}
+
+void OffscreenBuffer::updateMeshFromRegion() {
+    // avoid T-junctions as they cause artifacts in between the resultant
+    // geometry when complex transforms occur.
+    // TODO: generate the safeRegion only if necessary based on drawing transform
+    Region safeRegion = Region::createTJunctionFreeRegion(region);
+
+    size_t count;
+    const android::Rect* rects = safeRegion.getArray(&count);
+
+    const float texX = 1.0f / float(texture.width);
+    const float texY = 1.0f / float(texture.height);
+
+    FatVector<TextureVertex, 64> meshVector(count * 4); // uses heap if more than 64 vertices needed
+    TextureVertex* mesh = &meshVector[0];
+    for (size_t i = 0; i < count; i++) {
+        const android::Rect* r = &rects[i];
+
+        const float u1 = r->left * texX;
+        const float v1 = (viewportHeight - r->top) * texY;
+        const float u2 = r->right * texX;
+        const float v2 = (viewportHeight - r->bottom) * texY;
+
+        TextureVertex::set(mesh++, r->left, r->top, u1, v1);
+        TextureVertex::set(mesh++, r->right, r->top, u2, v1);
+        TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
+        TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
+    }
+    elementCount = count * 6;
+    renderState.meshState().genOrUpdateMeshBuffer(&vbo,
+            sizeof(TextureVertex) * count * 4,
+            &meshVector[0],
+            GL_DYNAMIC_DRAW); // TODO: GL_STATIC_DRAW if savelayer
+}
+
+uint32_t OffscreenBuffer::computeIdealDimension(uint32_t dimension) {
+    return uint32_t(ceilf(dimension / float(LAYER_SIZE)) * LAYER_SIZE);
+}
+
+OffscreenBuffer::~OffscreenBuffer() {
+    texture.deleteTexture();
+    renderState.meshState().deleteMeshBuffer(vbo);
+    elementCount = 0;
+    vbo = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// OffscreenBufferPool
+///////////////////////////////////////////////////////////////////////////////
+
+OffscreenBufferPool::OffscreenBufferPool()
+    : mMaxSize(Properties::layerPoolSize) {
+}
+
+OffscreenBufferPool::~OffscreenBufferPool() {
+    clear(); // TODO: unique_ptr?
+}
+
+int OffscreenBufferPool::Entry::compare(const Entry& lhs, const Entry& rhs) {
+    int deltaInt = int(lhs.width) - int(rhs.width);
+    if (deltaInt != 0) return deltaInt;
+
+    return int(lhs.height) - int(rhs.height);
+}
+
+void OffscreenBufferPool::clear() {
+    for (auto entry : mPool) {
+        delete entry.layer;
+    }
+    mPool.clear();
+    mSize = 0;
+}
+
+OffscreenBuffer* OffscreenBufferPool::get(RenderState& renderState,
+        const uint32_t width, const uint32_t height) {
+    OffscreenBuffer* layer = nullptr;
+
+    Entry entry(width, height);
+    auto iter = mPool.find(entry);
+
+    if (iter != mPool.end()) {
+        entry = *iter;
+        mPool.erase(iter);
+
+        layer = entry.layer;
+        layer->viewportWidth = width;
+        layer->viewportHeight = height;
+        mSize -= layer->getSizeInBytes();
+    } else {
+        layer = new OffscreenBuffer(renderState, Caches::getInstance(), width, height);
+    }
+
+    return layer;
+}
+
+OffscreenBuffer* OffscreenBufferPool::resize(OffscreenBuffer* layer,
+        const uint32_t width, const uint32_t height) {
+    RenderState& renderState = layer->renderState;
+    if (layer->texture.width == OffscreenBuffer::computeIdealDimension(width)
+            && layer->texture.height == OffscreenBuffer::computeIdealDimension(height)) {
+        // resize in place
+        layer->viewportWidth = width;
+        layer->viewportHeight = height;
+        return layer;
+    }
+    putOrDelete(layer);
+    return get(renderState, width, height);
+}
+
+void OffscreenBufferPool::dump() {
+    for (auto entry : mPool) {
+        ALOGD("  Layer size %dx%d", entry.width, entry.height);
+    }
+}
+
+void OffscreenBufferPool::putOrDelete(OffscreenBuffer* layer) {
+    const uint32_t size = layer->getSizeInBytes();
+    // Don't even try to cache a layer that's bigger than the cache
+    if (size < mMaxSize) {
+        // TODO: Use an LRU
+        while (mSize + size > mMaxSize) {
+            OffscreenBuffer* victim = mPool.begin()->layer;
+            mSize -= victim->getSizeInBytes();
+            delete victim;
+            mPool.erase(mPool.begin());
+        }
+
+        // clear region, since it's no longer valid
+        layer->region.clear();
+
+        Entry entry(layer);
+
+        mPool.insert(entry);
+        mSize += size;
+    } else {
+        delete layer;
+    }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h
new file mode 100644
index 0000000..f0fd82d
--- /dev/null
+++ b/libs/hwui/renderstate/OffscreenBufferPool.h
@@ -0,0 +1,142 @@
+/*
+ * 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 ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
+#define ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
+
+#include "Caches.h"
+#include "Texture.h"
+#include "utils/Macros.h"
+
+#include <ui/Region.h>
+
+#include <set>
+
+namespace android {
+namespace uirenderer {
+
+class RenderState;
+
+/**
+ * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
+ * encompasses enough information to draw it back on screen (minus paint properties, which are held
+ * by LayerOp).
+ */
+class OffscreenBuffer {
+public:
+    OffscreenBuffer(RenderState& renderState, Caches& caches,
+            uint32_t viewportWidth, uint32_t viewportHeight);
+    ~OffscreenBuffer();
+
+    // must be called prior to rendering, to construct/update vertex buffer
+    void updateMeshFromRegion();
+
+    static uint32_t computeIdealDimension(uint32_t dimension);
+
+    uint32_t getSizeInBytes() { return texture.width * texture.height * 4; }
+
+    RenderState& renderState;
+    uint32_t viewportWidth;
+    uint32_t viewportHeight;
+    Texture texture;
+
+    // Portion of layer that has been drawn to. Used to minimize drawing area when
+    // drawing back to screen / parent FBO.
+    Region region;
+    GLsizei elementCount = 0;
+    GLuint vbo = 0;
+};
+
+/**
+ * Pool of OffscreenBuffers allocated, but not currently in use.
+ */
+class OffscreenBufferPool {
+public:
+    OffscreenBufferPool();
+    ~OffscreenBufferPool();
+
+    WARN_UNUSED_RESULT OffscreenBuffer* get(RenderState& renderState,
+            const uint32_t width, const uint32_t height);
+
+    WARN_UNUSED_RESULT OffscreenBuffer* resize(OffscreenBuffer* layer,
+            const uint32_t width, const uint32_t height);
+
+    void putOrDelete(OffscreenBuffer* layer);
+
+    /**
+     * Clears the pool. This causes all layers to be deleted.
+     */
+    void clear();
+
+    /**
+     * Returns the maximum size of the pool in bytes.
+     */
+    uint32_t getMaxSize() { return mMaxSize; }
+
+    /**
+     * Returns the current size of the pool in bytes.
+     */
+    uint32_t getSize() { return mSize; }
+
+    size_t getCount() { return mPool.size(); }
+
+    /**
+     * Prints out the content of the pool.
+     */
+    void dump();
+private:
+    struct Entry {
+        Entry() {}
+
+        Entry(const uint32_t layerWidth, const uint32_t layerHeight)
+                : width(OffscreenBuffer::computeIdealDimension(layerWidth))
+                , height(OffscreenBuffer::computeIdealDimension(layerHeight)) {}
+
+        Entry(OffscreenBuffer* layer)
+                : layer(layer)
+                , width(layer->texture.width)
+                , height(layer->texture.height) {
+        }
+
+        static int compare(const Entry& lhs, const Entry& rhs);
+
+        bool operator==(const Entry& other) const {
+            return compare(*this, other) == 0;
+        }
+
+        bool operator!=(const Entry& other) const {
+            return compare(*this, other) != 0;
+        }
+
+        bool operator<(const Entry& other) const {
+            return Entry::compare(*this, other) < 0;
+        }
+
+        OffscreenBuffer* layer = nullptr;
+        uint32_t width = 0;
+        uint32_t height = 0;
+    }; // struct Entry
+
+    std::multiset<Entry> mPool;
+
+    uint32_t mSize = 0;
+    uint32_t mMaxSize;
+}; // class OffscreenBufferCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 9637117..4fa8200 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -90,6 +90,8 @@
     }
 */
 
+    mLayerPool.clear();
+
     // TODO: reset all cached state in state objects
     std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext);
     mAssetAtlas.terminate();
@@ -106,6 +108,19 @@
     mStencil = nullptr;
 }
 
+void RenderState::flush(Caches::FlushMode mode) {
+    switch (mode) {
+        case Caches::FlushMode::Full:
+            // fall through
+        case Caches::FlushMode::Moderate:
+            // fall through
+        case Caches::FlushMode::Layers:
+            mLayerPool.clear();
+            break;
+    }
+    mCaches->flush(mode);
+}
+
 void RenderState::setViewport(GLsizei width, GLsizei height) {
     mViewportWidth = width;
     mViewportHeight = height;
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index 3cda170..dcd5ea6 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -21,6 +21,7 @@
 #include "Glop.h"
 #include "renderstate/Blend.h"
 #include "renderstate/MeshState.h"
+#include "renderstate/OffscreenBufferPool.h"
 #include "renderstate/PixelBufferState.h"
 #include "renderstate/Scissor.h"
 #include "renderstate/Stencil.h"
@@ -56,6 +57,8 @@
     void onGLContextCreated();
     void onGLContextDestroyed();
 
+    void flush(Caches::FlushMode flushMode);
+
     void setViewport(GLsizei width, GLsizei height);
     void getViewport(GLsizei* outWidth, GLsizei* outHeight);
 
@@ -97,6 +100,8 @@
     Scissor& scissor() { return *mScissor; }
     Stencil& stencil() { return *mStencil; }
 
+    OffscreenBufferPool& layerPool() { return mLayerPool; }
+
     void dump();
 
 private:
@@ -116,6 +121,8 @@
     Scissor* mScissor = nullptr;
     Stencil* mStencil = nullptr;
 
+    OffscreenBufferPool mLayerPool;
+
     AssetAtlas mAssetAtlas;
     std::set<Layer*> mActiveLayers;
     std::set<renderthread::CanvasContext*> mRegisteredContexts;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 1b89960..f094b2d 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -599,7 +599,7 @@
         // Make sure to release all the textures we were owning as there won't
         // be another draw
         caches.textureCache.resetMarkInUse(this);
-        caches.flush(Caches::FlushMode::Layers);
+        mRenderThread.renderState().flush(Caches::FlushMode::Layers);
     }
 }
 
@@ -609,10 +609,10 @@
 
     ATRACE_CALL();
     if (level >= TRIM_MEMORY_COMPLETE) {
-        Caches::getInstance().flush(Caches::FlushMode::Full);
+        thread.renderState().flush(Caches::FlushMode::Full);
         thread.eglManager().destroy();
     } else if (level >= TRIM_MEMORY_UI_HIDDEN) {
-        Caches::getInstance().flush(Caches::FlushMode::Moderate);
+        thread.renderState().flush(Caches::FlushMode::Moderate);
     }
 }
 
diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp
index 29d9803..81bf9ed 100644
--- a/libs/hwui/tests/TreeContentAnimation.cpp
+++ b/libs/hwui/tests/TreeContentAnimation.cpp
@@ -408,7 +408,7 @@
 public:
     sp<RenderNode> card = TestUtils::createNode<TestCanvas>(0, 0, 200, 200, [] (TestCanvas& canvas) {
         canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
-    }, true);
+    }, TestUtils::getHwLayerSetupCallback());
     void createContent(int width, int height, TestCanvas* canvas) override {
         canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
         canvas->drawRenderNode(card.get());
diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp
index bc1b69f..4e00fb3 100644
--- a/libs/hwui/unit_tests/BakedOpStateTests.cpp
+++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp
@@ -60,24 +60,21 @@
 TEST(BakedOpState, constructAndReject) {
     LinearAllocator allocator;
 
-    Matrix4 identity;
-    identity.loadIdentity();
-
     Matrix4 translate100x0;
     translate100x0.loadTranslate(100, 0, 0);
 
     SkPaint paint;
     {
         RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, Rect(0, 0, 100, 200), &paint);
-        auto snapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
+        auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 200));
         BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, rejectOp);
 
         EXPECT_EQ(bakedOp, nullptr); // rejected by clip, so not constructed
         EXPECT_LE(allocator.usedSize(), 8u); // no significant allocation space used for rejected op
     }
     {
-        RectOp successOp(Rect(30, 40, 100, 200), identity, Rect(0, 0, 100, 200), &paint);
-        auto snapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
+        RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), Rect(0, 0, 100, 200), &paint);
+        auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 200));
         BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, successOp);
 
         EXPECT_NE(bakedOp, nullptr); // NOT rejected by clip, so will be constructed
@@ -85,5 +82,24 @@
     }
 }
 
+TEST(BakedOpState, oplessConstructAndReject) {
+    LinearAllocator allocator;
+    {
+        auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 0, 0)); // empty
+        BakedOpState* bakedOp = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234);
+
+        EXPECT_EQ(bakedOp, nullptr); // rejected by clip, so not constructed
+        EXPECT_LE(allocator.usedSize(), 8u); // no significant allocation space used for rejected op
+    }
+    {
+        auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 200));
+        BakedOpState* bakedOp = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234);
+
+        EXPECT_NE(bakedOp, nullptr); // NOT rejected by clip, so will be constructed
+        EXPECT_GT(allocator.usedSize(), 64u); // relatively large alloc for non-rejected op
+        EXPECT_EQ((ShadowOp*)0x1234, bakedOp->op);
+    }
+}
+
 }
 }
diff --git a/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
new file mode 100644
index 0000000..ba92157
--- /dev/null
+++ b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
@@ -0,0 +1,121 @@
+/*
+ * 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 <renderstate/OffscreenBufferPool.h>
+
+#include <unit_tests/TestUtils.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+TEST(OffscreenBuffer, computeIdealDimension) {
+    EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(1));
+    EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(31));
+    EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(33));
+    EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(64));
+    EXPECT_EQ(1024u, OffscreenBuffer::computeIdealDimension(1000));
+}
+
+TEST(OffscreenBuffer, construct) {
+    TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+        OffscreenBuffer layer(thread.renderState(), Caches::getInstance(), 49u, 149u);
+        EXPECT_EQ(49u, layer.viewportWidth);
+        EXPECT_EQ(149u, layer.viewportHeight);
+
+        EXPECT_EQ(64u, layer.texture.width);
+        EXPECT_EQ(192u, layer.texture.height);
+
+        EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes());
+    });
+}
+
+TEST(OffscreenBufferPool, construct) {
+    TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+        OffscreenBufferPool pool;
+        EXPECT_EQ(0u, pool.getCount()) << "pool must be created empty";
+        EXPECT_EQ(0u, pool.getSize()) << "pool must be created empty";
+        EXPECT_EQ((uint32_t) Properties::layerPoolSize, pool.getMaxSize())
+                << "pool must read size from Properties";
+    });
+
+}
+
+TEST(OffscreenBufferPool, getPutClear) {
+    TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+        OffscreenBufferPool pool;
+
+        auto layer = pool.get(thread.renderState(), 100u, 200u);
+        EXPECT_EQ(100u, layer->viewportWidth);
+        EXPECT_EQ(200u, layer->viewportHeight);
+
+        ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize());
+
+        pool.putOrDelete(layer);
+        ASSERT_EQ(layer->getSizeInBytes(), pool.getSize());
+
+        auto layer2 = pool.get(thread.renderState(), 102u, 202u);
+        EXPECT_EQ(layer, layer2) << "layer should be recycled";
+        ASSERT_EQ(0u, pool.getSize()) << "pool should have been emptied by removing only layer";
+
+        pool.putOrDelete(layer);
+        EXPECT_EQ(1u, pool.getCount());
+        pool.clear();
+        EXPECT_EQ(0u, pool.getSize());
+        EXPECT_EQ(0u, pool.getCount());
+    });
+}
+
+TEST(OffscreenBufferPool, resize) {
+    TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+        OffscreenBufferPool pool;
+
+        auto layer = pool.get(thread.renderState(), 64u, 64u);
+
+        // resize in place
+        ASSERT_EQ(layer, pool.resize(layer, 60u, 55u));
+        EXPECT_EQ(60u, layer->viewportWidth);
+        EXPECT_EQ(55u, layer->viewportHeight);
+        EXPECT_EQ(64u, layer->texture.width);
+        EXPECT_EQ(64u, layer->texture.height);
+
+        // resized to use different object in pool
+        auto layer2 = pool.get(thread.renderState(), 128u, 128u);
+        pool.putOrDelete(layer2);
+        ASSERT_EQ(1u, pool.getCount());
+        ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u));
+        EXPECT_EQ(120u, layer2->viewportWidth);
+        EXPECT_EQ(125u, layer2->viewportHeight);
+        EXPECT_EQ(128u, layer2->texture.width);
+        EXPECT_EQ(128u, layer2->texture.height);
+
+        // original allocation now only thing in pool
+        EXPECT_EQ(1u, pool.getCount());
+        EXPECT_EQ(layer->getSizeInBytes(), pool.getSize());
+    });
+}
+
+TEST(OffscreenBufferPool, putAndDestroy) {
+    TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+        OffscreenBufferPool pool;
+        // layer too big to return to the pool
+        // Note: this relies on the fact that the pool won't reject based on max texture size
+        auto hugeLayer = pool.get(thread.renderState(), pool.getMaxSize() / 64, 64);
+        EXPECT_GT(hugeLayer->getSizeInBytes(), pool.getMaxSize());
+        pool.putOrDelete(hugeLayer);
+        EXPECT_EQ(0u, pool.getCount()); // failed to put (so was destroyed instead)
+    });
+}
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index f67c24a..07080a2 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -38,17 +38,17 @@
  * and allows Renderer vs Dispatching behavior to be merged.
  *
  * onXXXOp methods fail by default - tests should override ops they expect
- * startLayer fails by default - tests should override if expected
+ * startRepaintLayer fails by default - tests should override if expected
  * startFrame/endFrame do nothing by default - tests should override to intercept
  */
 class TestRendererBase {
 public:
     virtual ~TestRendererBase() {}
-    virtual OffscreenBuffer* createLayer(uint32_t, uint32_t) {
+    virtual OffscreenBuffer* startTemporaryLayer(uint32_t, uint32_t) {
         ADD_FAILURE() << "Layer creation not expected in this test";
         return nullptr;
     }
-    virtual void startLayer(OffscreenBuffer*) {
+    virtual void startRepaintLayer(OffscreenBuffer*) {
         ADD_FAILURE() << "Layer repaint not expected in this test";
     }
     virtual void endLayer() {
@@ -82,27 +82,27 @@
     MAP_OPS(DISPATCHER_METHOD);
 };
 
-
 class FailRenderer : public TestRendererBase {};
 
-class SimpleTestRenderer : public TestRendererBase {
-public:
-    void startFrame(uint32_t width, uint32_t height) override {
-        EXPECT_EQ(0, mIndex++);
-        EXPECT_EQ(100u, width);
-        EXPECT_EQ(200u, height);
-    }
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(1, mIndex++);
-    }
-    void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(2, mIndex++);
-    }
-    void endFrame() override {
-        EXPECT_EQ(3, mIndex++);
-    }
-};
 TEST(OpReorderer, simple) {
+    class SimpleTestRenderer : public TestRendererBase {
+    public:
+        void startFrame(uint32_t width, uint32_t height) override {
+            EXPECT_EQ(0, mIndex++);
+            EXPECT_EQ(100u, width);
+            EXPECT_EQ(200u, height);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+        }
+        void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void endFrame() override {
+            EXPECT_EQ(3, mIndex++);
+        }
+    };
+
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
         SkBitmap bitmap = TestUtils::createSkBitmap(25, 25);
         canvas.drawRect(0, 0, 100, 200, SkPaint());
@@ -115,7 +115,6 @@
     EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
 }
 
-
 TEST(OpReorderer, simpleRejection) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
@@ -129,18 +128,18 @@
     reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
-
-static int SIMPLE_BATCHING_LOOPS = 5;
-class SimpleBatchingTestRenderer : public TestRendererBase {
-public:
-    void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
-        EXPECT_TRUE(mIndex++ >= SIMPLE_BATCHING_LOOPS);
-    }
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        EXPECT_TRUE(mIndex++ < SIMPLE_BATCHING_LOOPS);
-    }
-};
 TEST(OpReorderer, simpleBatching) {
+    static int SIMPLE_BATCHING_LOOPS = 5;
+    class SimpleBatchingTestRenderer : public TestRendererBase {
+    public:
+        void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+            EXPECT_TRUE(mIndex++ >= SIMPLE_BATCHING_LOOPS);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_TRUE(mIndex++ < SIMPLE_BATCHING_LOOPS);
+        }
+    };
+
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
 
@@ -162,24 +161,25 @@
     EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, renderer.getIndex()); // 2 x loops ops, because no merging (TODO: force no merging)
 }
 
-class RenderNodeTestRenderer : public TestRendererBase {
-public:
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        switch(mIndex++) {
-        case 0:
-            EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clippedBounds);
-            EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
-            break;
-        case 1:
-            EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds);
-            EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
-            break;
-        default:
-            ADD_FAILURE();
-        }
-    }
-};
 TEST(OpReorderer, renderNode) {
+    class RenderNodeTestRenderer : public TestRendererBase {
+    public:
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            switch(mIndex++) {
+            case 0:
+                EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clippedBounds);
+                EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
+                break;
+            case 1:
+                EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds);
+                EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+                break;
+            default:
+                ADD_FAILURE();
+            }
+        }
+    };
+
     sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
         SkPaint paint;
         paint.setColor(SK_ColorWHITE);
@@ -210,16 +210,17 @@
     reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
-class ClippedTestRenderer : public TestRendererBase {
-public:
-    void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(0, mIndex++);
-        EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds);
-        EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect);
-        EXPECT_TRUE(state.computedState.transform.isIdentity());
-    }
-};
 TEST(OpReorderer, clipped) {
+    class ClippedTestRenderer : public TestRendererBase {
+    public:
+        void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(0, mIndex++);
+            EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds);
+            EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect);
+            EXPECT_TRUE(state.computedState.transform.isIdentity());
+        }
+    };
+
     sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RecordingCanvas& canvas) {
         SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
         canvas.drawBitmap(bitmap, 0, 0, nullptr);
@@ -236,36 +237,36 @@
     reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
-
-class SaveLayerSimpleTestRenderer : public TestRendererBase {
-public:
-    OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override {
-        EXPECT_EQ(0, mIndex++);
-        EXPECT_EQ(180u, width);
-        EXPECT_EQ(180u, height);
-        return nullptr;
-    }
-    void endLayer() override {
-        EXPECT_EQ(2, mIndex++);
-    }
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(1, mIndex++);
-        EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
-        EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clippedBounds);
-        EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clipRect);
-
-        Matrix4 expectedTransform;
-        expectedTransform.loadTranslate(-10, -10, 0);
-        EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform);
-    }
-    void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(3, mIndex++);
-        EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
-        EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clipRect);
-        EXPECT_TRUE(state.computedState.transform.isIdentity());
-    }
-};
 TEST(OpReorderer, saveLayerSimple) {
+    class SaveLayerSimpleTestRenderer : public TestRendererBase {
+    public:
+        OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+            EXPECT_EQ(0, mIndex++);
+            EXPECT_EQ(180u, width);
+            EXPECT_EQ(180u, height);
+            return nullptr;
+        }
+        void endLayer() override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+            EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
+            EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clippedBounds);
+            EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clipRect);
+
+            Matrix4 expectedTransform;
+            expectedTransform.loadTranslate(-10, -10, 0);
+            EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform);
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(3, mIndex++);
+            EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
+            EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clipRect);
+            EXPECT_TRUE(state.computedState.transform.isIdentity());
+        }
+    };
+
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         canvas.saveLayerAlpha(10, 10, 190, 190, 128, SkCanvas::kClipToLayer_SaveFlag);
         canvas.drawRect(10, 10, 190, 190, SkPaint());
@@ -279,57 +280,57 @@
     EXPECT_EQ(4, renderer.getIndex());
 }
 
-
-/* saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as:
- * - createLayer2, rect2 endLayer2
- * - createLayer1, rect1, drawLayer2, endLayer1
- * - startFrame, layerOp1, endFrame
- */
-class SaveLayerNestedTestRenderer : public TestRendererBase {
-public:
-    OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override {
-        const int index = mIndex++;
-        if (index == 0) {
-            EXPECT_EQ(400u, width);
-            EXPECT_EQ(400u, height);
-            return (OffscreenBuffer*) 0x400;
-        } else if (index == 3) {
-            EXPECT_EQ(800u, width);
-            EXPECT_EQ(800u, height);
-            return (OffscreenBuffer*) 0x800;
-        } else { ADD_FAILURE(); }
-        return (OffscreenBuffer*) nullptr;
-    }
-    void endLayer() override {
-        int index = mIndex++;
-        EXPECT_TRUE(index == 2 || index == 6);
-    }
-    void startFrame(uint32_t width, uint32_t height) override {
-        EXPECT_EQ(7, mIndex++);
-    }
-    void endFrame() override {
-        EXPECT_EQ(9, mIndex++);
-    }
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        const int index = mIndex++;
-        if (index == 1) {
-            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect
-        } else if (index == 4) {
-            EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect
-        } else { ADD_FAILURE(); }
-    }
-    void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
-        const int index = mIndex++;
-        if (index == 5) {
-            EXPECT_EQ((OffscreenBuffer*)0x400, *op.layerHandle);
-            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
-        } else if (index == 8) {
-            EXPECT_EQ((OffscreenBuffer*)0x800, *op.layerHandle);
-            EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer
-        } else { ADD_FAILURE(); }
-    }
-};
 TEST(OpReorderer, saveLayerNested) {
+    /* saveLayer1 { rect1, saveLayer2 { rect2 } } will play back as:
+     * - startTemporaryLayer2, rect2 endLayer2
+     * - startTemporaryLayer1, rect1, drawLayer2, endLayer1
+     * - startFrame, layerOp1, endFrame
+     */
+    class SaveLayerNestedTestRenderer : public TestRendererBase {
+    public:
+        OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+            const int index = mIndex++;
+            if (index == 0) {
+                EXPECT_EQ(400u, width);
+                EXPECT_EQ(400u, height);
+                return (OffscreenBuffer*) 0x400;
+            } else if (index == 3) {
+                EXPECT_EQ(800u, width);
+                EXPECT_EQ(800u, height);
+                return (OffscreenBuffer*) 0x800;
+            } else { ADD_FAILURE(); }
+            return (OffscreenBuffer*) nullptr;
+        }
+        void endLayer() override {
+            int index = mIndex++;
+            EXPECT_TRUE(index == 2 || index == 6);
+        }
+        void startFrame(uint32_t width, uint32_t height) override {
+            EXPECT_EQ(7, mIndex++);
+        }
+        void endFrame() override {
+            EXPECT_EQ(9, mIndex++);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            const int index = mIndex++;
+            if (index == 1) {
+                EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect
+            } else if (index == 4) {
+                EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect
+            } else { ADD_FAILURE(); }
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            const int index = mIndex++;
+            if (index == 5) {
+                EXPECT_EQ((OffscreenBuffer*)0x400, *op.layerHandle);
+                EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
+            } else if (index == 8) {
+                EXPECT_EQ((OffscreenBuffer*)0x800, *op.layerHandle);
+                EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer
+            } else { ADD_FAILURE(); }
+        }
+    };
+
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(800, 800, [](RecordingCanvas& canvas) {
         canvas.saveLayerAlpha(0, 0, 800, 800, 128, SkCanvas::kClipToLayer_SaveFlag);
         {
@@ -369,42 +370,41 @@
     reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
-class HwLayerSimpleTestRenderer : public TestRendererBase {
-public:
-    void startLayer(OffscreenBuffer* offscreenBuffer) override {
-        EXPECT_EQ(0, mIndex++);
-        EXPECT_EQ(offscreenBuffer, (OffscreenBuffer*) 0x0124);
-    }
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(1, mIndex++);
-
-        EXPECT_TRUE(state.computedState.transform.isIdentity())
-                << "Transform should be reset within layer";
-
-        EXPECT_EQ(state.computedState.clipRect, Rect(25, 25, 75, 75))
-                << "Damage rect should be used to clip layer content";
-    }
-    void endLayer() override {
-        EXPECT_EQ(2, mIndex++);
-    }
-    void startFrame(uint32_t width, uint32_t height) override {
-        EXPECT_EQ(3, mIndex++);
-    }
-    void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(4, mIndex++);
-    }
-    void endFrame() override {
-        EXPECT_EQ(5, mIndex++);
-    }
-};
 TEST(OpReorderer, hwLayerSimple) {
+    class HwLayerSimpleTestRenderer : public TestRendererBase {
+    public:
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer) override {
+            EXPECT_EQ(0, mIndex++);
+            EXPECT_EQ(offscreenBuffer, (OffscreenBuffer*) 0x0124);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+
+            EXPECT_TRUE(state.computedState.transform.isIdentity())
+                    << "Transform should be reset within layer";
+
+            EXPECT_EQ(state.computedState.clipRect, Rect(25, 25, 75, 75))
+                    << "Damage rect should be used to clip layer content";
+        }
+        void endLayer() override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void startFrame(uint32_t width, uint32_t height) override {
+            EXPECT_EQ(3, mIndex++);
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(4, mIndex++);
+        }
+        void endFrame() override {
+            EXPECT_EQ(5, mIndex++);
+        }
+    };
+
     sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
         SkPaint paint;
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
-    });
-    node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
-    node->setPropertyFieldsDirty(RenderNode::GENERIC);
+    }, TestUtils::getHwLayerSetupCallback());
     OffscreenBuffer** bufferHandle = node->getLayerHandle();
     *bufferHandle = (OffscreenBuffer*) 0x0124;
 
@@ -427,69 +427,67 @@
     *bufferHandle = nullptr;
 }
 
-
-/* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
- * - startLayer(child), rect(grey), endLayer
- * - createLayer, drawLayer(child), endLayer
- * - startLayer(parent), rect(white), drawLayer(saveLayer), endLayer
- * - startFrame, drawLayer(parent), endLayerb
- */
-class HwLayerComplexTestRenderer : public TestRendererBase {
-public:
-    OffscreenBuffer* createLayer(uint32_t width, uint32_t height) {
-        EXPECT_EQ(3, mIndex++); // savelayer first
-        return (OffscreenBuffer*)0xabcd;
-    }
-    void startLayer(OffscreenBuffer* offscreenBuffer) override {
-        int index = mIndex++;
-        if (index == 0) {
-            // starting inner layer
-            EXPECT_EQ((OffscreenBuffer*)0x4567, offscreenBuffer);
-        } else if (index == 6) {
-            // starting outer layer
-            EXPECT_EQ((OffscreenBuffer*)0x0123, offscreenBuffer);
-        } else { ADD_FAILURE(); }
-    }
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        int index = mIndex++;
-        if (index == 1) {
-            // inner layer's rect (white)
-            EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
-        } else if (index == 7) {
-            // outer layer's rect (grey)
-            EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
-        } else { ADD_FAILURE(); }
-    }
-    void endLayer() override {
-        int index = mIndex++;
-        EXPECT_TRUE(index == 2 || index == 5 || index == 9);
-    }
-    void startFrame(uint32_t width, uint32_t height) override {
-        EXPECT_EQ(10, mIndex++);
-    }
-    void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
-        int index = mIndex++;
-        if (index == 4) {
-            EXPECT_EQ((OffscreenBuffer*)0x4567, *op.layerHandle);
-        } else if (index == 8) {
-            EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
-        } else if (index == 11) {
-            EXPECT_EQ((OffscreenBuffer*)0x0123, *op.layerHandle);
-        } else { ADD_FAILURE(); }
-    }
-    void endFrame() override {
-        EXPECT_EQ(12, mIndex++);
-    }
-};
 TEST(OpReorderer, hwLayerComplex) {
+    /* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
+     * - startRepaintLayer(child), rect(grey), endLayer
+     * - startTemporaryLayer, drawLayer(child), endLayer
+     * - startRepaintLayer(parent), rect(white), drawLayer(saveLayer), endLayer
+     * - startFrame, drawLayer(parent), endLayerb
+     */
+    class HwLayerComplexTestRenderer : public TestRendererBase {
+    public:
+        OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) {
+            EXPECT_EQ(3, mIndex++); // savelayer first
+            return (OffscreenBuffer*)0xabcd;
+        }
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer) override {
+            int index = mIndex++;
+            if (index == 0) {
+                // starting inner layer
+                EXPECT_EQ((OffscreenBuffer*)0x4567, offscreenBuffer);
+            } else if (index == 6) {
+                // starting outer layer
+                EXPECT_EQ((OffscreenBuffer*)0x0123, offscreenBuffer);
+            } else { ADD_FAILURE(); }
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            int index = mIndex++;
+            if (index == 1) {
+                // inner layer's rect (white)
+                EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+            } else if (index == 7) {
+                // outer layer's rect (grey)
+                EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
+            } else { ADD_FAILURE(); }
+        }
+        void endLayer() override {
+            int index = mIndex++;
+            EXPECT_TRUE(index == 2 || index == 5 || index == 9);
+        }
+        void startFrame(uint32_t width, uint32_t height) override {
+            EXPECT_EQ(10, mIndex++);
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            int index = mIndex++;
+            if (index == 4) {
+                EXPECT_EQ((OffscreenBuffer*)0x4567, *op.layerHandle);
+            } else if (index == 8) {
+                EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
+            } else if (index == 11) {
+                EXPECT_EQ((OffscreenBuffer*)0x0123, *op.layerHandle);
+            } else { ADD_FAILURE(); }
+        }
+        void endFrame() override {
+            EXPECT_EQ(12, mIndex++);
+        }
+    };
+
     auto child = TestUtils::createNode<RecordingCanvas>(50, 50, 150, 150,
             [](RecordingCanvas& canvas) {
         SkPaint paint;
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
-    });
-    child->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
-    child->setPropertyFieldsDirty(RenderNode::GENERIC);
+    }, TestUtils::getHwLayerSetupCallback());
     *(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567;
 
     RenderNode* childPtr = child.get();
@@ -502,9 +500,7 @@
         canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
         canvas.drawRenderNode(childPtr);
         canvas.restore();
-    });
-    parent->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
-    parent->setPropertyFieldsDirty(RenderNode::GENERIC);
+    }, TestUtils::getHwLayerSetupCallback());
     *(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123;
 
     TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
@@ -527,14 +523,6 @@
     *(parent->getLayerHandle()) = nullptr;
 }
 
-
-class ZReorderTestRenderer : public TestRendererBase {
-public:
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
-        EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
-    }
-};
 static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder) {
     SkPaint paint;
     paint.setColor(SkColorSetARGB(256, 0, 0, expectedDrawOrder)); // order put in blue channel
@@ -550,6 +538,14 @@
     canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
 }
 TEST(OpReorderer, zReorder) {
+    class ZReorderTestRenderer : public TestRendererBase {
+    public:
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
+            EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+        }
+    };
+
     auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
             [](RecordingCanvas& canvas) {
         drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
@@ -576,27 +572,64 @@
     EXPECT_EQ(10, renderer.getIndex());
 };
 
+TEST(OpReorderer, shadow) {
+    class ShadowTestRenderer : public TestRendererBase {
+    public:
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(0, mIndex++);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+        }
+    };
 
-class PropertyTestRenderer : public TestRendererBase {
-public:
-    PropertyTestRenderer(std::function<void(const RectOp&, const BakedOpState&)> callback)
-            : mCallback(callback) {}
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(mIndex++, 0);
-        mCallback(op, state);
-    }
-    std::function<void(const RectOp&, const BakedOpState&)> mCallback;
-};
+    sp<RenderNode> caster = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setColor(SK_ColorWHITE);
+        canvas.drawRect(0, 0, 100, 100, paint);
+    }, [] (RenderProperties& properties) {
+        properties.setTranslationZ(5.0f);
+        properties.mutableOutline().setRoundRect(0, 0, 100, 100, 5, 1.0f);
+        return RenderNode::GENERIC | RenderNode::TRANSLATION_Z;
+    });
+    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+            [&caster] (RecordingCanvas& canvas) {
+        canvas.insertReorderBarrier(true);
+        canvas.drawRenderNode(caster.get());
+    });
+
+    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+
+    std::vector< sp<RenderNode> > nodes;
+    nodes.push_back(parent.get());
+
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+
+    ShadowTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2, renderer.getIndex());
+}
 
 static void testProperty(
-        std::function<int(RenderProperties&)> propSetupCallback,
+        TestUtils::PropSetupCallback propSetupCallback,
         std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
+    class PropertyTestRenderer : public TestRendererBase {
+    public:
+        PropertyTestRenderer(std::function<void(const RectOp&, const BakedOpState&)> callback)
+                : mCallback(callback) {}
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(mIndex++, 0);
+            mCallback(op, state);
+        }
+        std::function<void(const RectOp&, const BakedOpState&)> mCallback;
+    };
+
     auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100, [](RecordingCanvas& canvas) {
         SkPaint paint;
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
-    });
-    node->setPropertyFieldsDirty(propSetupCallback(node->mutateStagingProperties()));
+    }, propSetupCallback);
     TestUtils::syncHierarchyPropertiesAndDisplayList(node);
 
     std::vector< sp<RenderNode> > nodes;
diff --git a/libs/hwui/unit_tests/PathParserTests.cpp b/libs/hwui/unit_tests/PathParserTests.cpp
index 244bd6c..60ea219 100644
--- a/libs/hwui/unit_tests/PathParserTests.cpp
+++ b/libs/hwui/unit_tests/PathParserTests.cpp
@@ -30,142 +30,179 @@
     const std::function<void(SkPath*)> skPathLamda;
 };
 
-static TestData testData1 {
-    // Path
-    "M2.000000,22.000000l20.000000,0.000000 1e0-2e3z",
+const static TestData sTestDataSet[] = {
+    // TestData with scientific notation -2e3 etc.
     {
-        // Verbs
-        {'M', 'l', 'z'},
-        // Verb sizes
-        {2, 4, 0},
-        // Points
-        {2, 22, 20, 0,  1, -2000},
+        // Path
+        "M2.000000,22.000000l20.000000,0.000000 1e0-2e3z",
+        {
+            // Verbs
+            {'M', 'l', 'z'},
+            // Verb sizes
+            {2, 4, 0},
+            // Points
+            {2, 22, 20, 0,  1, -2000},
+        },
+        [](SkPath* outPath) {
+            outPath->moveTo(2, 22);
+            outPath->rLineTo(20, 0);
+            outPath->rLineTo(1, -2000);
+            outPath->close();
+            outPath->moveTo(2, 22);
+        }
     },
-    [](SkPath* outPath) {
-        outPath->moveTo(2, 22);
-        outPath->rLineTo(20, 0);
-        outPath->rLineTo(1, -2000);
-        outPath->close();
-        outPath->moveTo(2, 22);
+
+    // Comprehensive data, containing all the verbs possible.
+    {
+        // Path
+        "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10",
+        {
+            // Verbs
+            {'M', 'm', 'l', 'L', 'H', 'h', 'V', 'v', 'Q', 'q', 't', 'T', 'C', 'c', 'S', 's', 'A', 'a'},
+            // VerbSizes
+            {2, 2, 2, 2, 1, 1, 1, 1, 4, 4, 2, 2, 6, 6, 4, 4, 7, 7},
+            // Points
+            {1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 7.0, 7.0, 7.0, 7.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, }
+        },
+        [](SkPath* outPath) {
+            outPath->moveTo(1.0, 1.0);
+            outPath->rMoveTo(2.0, 2.0);
+            outPath->rLineTo(3.0, 3.0);
+            outPath->lineTo(3.0, 3.0);
+            outPath->lineTo(4.0, 3.0);
+            outPath->rLineTo(4.0, 0);
+            outPath->lineTo(8.0, 5.0);
+            outPath->rLineTo(0, 5.0);
+            outPath->quadTo(6.0, 6.0, 6.0, 6.0);
+            outPath->rQuadTo(6.0, 6.0, 6.0, 6.0);
+            outPath->rQuadTo(0.0, 0.0, 7.0, 7.0);
+            outPath->quadTo(26.0, 26.0, 7.0, 7.0);
+            outPath->cubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0);
+            outPath->rCubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0);
+            outPath->cubicTo(16.0, 16.0, 9.0, 9.0, 9.0, 9.0);
+            outPath->rCubicTo(0.0, 0.0, 9.0, 9.0, 9.0, 9.0);
+            outPath->cubicTo(18.447775037328352, 20.404243860300607, 17.998389141249767, 22.8911717921705, 16.737515350332117, 24.986664170401575);
+            outPath->cubicTo(15.476641559414468, 27.08215654863265, 13.489843598291483, 28.644011882390082, 11.155893964798905, 29.37447073281729);
+            outPath->cubicTo(8.821944331306327, 30.1049295832445, 6.299226382436471, 29.954422532383525, 4.0686829203897235, 28.951642951534332);
+            outPath->cubicTo(1.838139458342976, 27.94886337068514, 0.05113662931485696, 26.161860541657013, -0.9516429515343354, 23.931317079610267);
+            outPath->cubicTo(-1.9544225323835278, 21.70077361756352, -2.1049295832444987, 19.178055668693663, -1.37447073281729, 16.844106035201087);
+            outPath->cubicTo(-0.6440118823900814, 14.51015640170851, 0.9178434513673546, 12.523358440585524, 3.0133358295984305, 11.262484649667876);
+            outPath->cubicTo(5.108828207829506, 10.001610858750228, 7.5957561396993984, 9.552224962671648, 10.000000000000005, 10.0);
+            outPath->cubicTo(10.0, 7.348852265086975, 11.054287646850167, 4.803576729418881, 12.928932188134523, 2.9289321881345254);
+            outPath->cubicTo(14.803576729418879, 1.0542876468501696, 17.348852265086972, 4.870079381441987E-16, 19.999999999999996, 0.0);
+            outPath->cubicTo(22.65114773491302, -4.870079381441987E-16, 25.19642327058112, 1.0542876468501678, 27.071067811865476, 2.9289321881345227);
+            outPath->cubicTo(28.94571235314983, 4.803576729418878, 30.0, 7.348852265086974, 30.0, 9.999999999999998);
+            outPath->cubicTo(30.0, 12.651147734913023, 28.94571235314983, 15.19642327058112, 27.071067811865476, 17.071067811865476);
+            outPath->cubicTo(25.19642327058112, 18.94571235314983, 22.651147734913028, 20.0, 20.000000000000004, 20.0);
+        }
+    },
+
+    // Random long data
+    {
+        // Path
+        "M5.3,13.2c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,-0.2 -0.4,-0.7 -0.2,-1.0c1.3,-1.9 2.9,-3.4 4.9,-4.5c4.1,-2.2 9.3,-2.2 13.4,0.0c1.9,1.1 3.6,2.5 4.9,4.4c0.2,0.3 0.1,0.8 -0.2,1.0c-0.3,0.2 -0.8,0.1 -1.0,-0.2c-1.2,-1.7 -2.6,-3.0 -4.3,-4.0c-3.7,-2.0 -8.3,-2.0 -12.0,0.0c-1.7,0.9 -3.2,2.3 -4.3,4.0C5.7,13.1 5.5,13.2 5.3,13.2z",
+        {
+            // Verbs
+            {'M', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'C', 'z'},
+            // Verb sizes
+            {2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0},
+            // Points
+            {5.3, 13.2, -0.1, 0, -0.3, 0, -0.4, -0.1, -0.3, -0.2, -0.4, -0.7, -0.2, -1, 1.3, -1.9, 2.9, -3.4, 4.9, -4.5, 4.1, -2.2, 9.3, -2.2, 13.4, 0, 1.9, 1.1, 3.6, 2.5, 4.9, 4.4, 0.2, 0.3, 0.1, 0.8, -0.2, 1, -0.3, 0.2, -0.8, 0.1, -1, -0.2, -1.2, -1.7, -2.6, -3, -4.3, -4, -3.7, -2, -8.3, -2, -12, 0, -1.7, 0.9, -3.2, 2.3, -4.3, 4, 5.7, 13.1, 5.5, 13.2, 5.3, 13.2},
+        },
+        [](SkPath* outPath) {
+            outPath->moveTo(5.3, 13.2);
+            outPath->rCubicTo(-0.1, 0.0, -0.3, 0.0, -0.4, -0.1);
+            outPath->rCubicTo(-0.3, -0.2, -0.4, -0.7, -0.2, -1.0);
+            outPath->rCubicTo(1.3, -1.9, 2.9, -3.4, 4.9, -4.5);
+            outPath->rCubicTo(4.1, -2.2, 9.3, -2.2, 13.4, 0.0);
+            outPath->rCubicTo(1.9, 1.1, 3.6, 2.5, 4.9, 4.4);
+            outPath->rCubicTo(0.2, 0.3, 0.1, 0.8, -0.2, 1.0);
+            outPath->rCubicTo(-0.3, 0.2, -0.8, 0.1, -1.0, -0.2);
+            outPath->rCubicTo(-1.2, -1.7, -2.6, -3.0, -4.3, -4.0);
+            outPath->rCubicTo(-3.7, -2.0, -8.3, -2.0, -12.0, 0.0);
+            outPath->rCubicTo(-1.7, 0.9, -3.2, 2.3, -4.3, 4.0);
+            outPath->cubicTo(5.7, 13.1, 5.5, 13.2, 5.3, 13.2);
+            outPath->close();
+            outPath->moveTo(5.3, 13.2);
+        }
+    },
+
+    // Extreme case with numbers and decimal points crunched together
+    {
+        // Path
+        "l0.0.0.5.0.0.5-0.5.0.0-.5z",
+        {
+            // Verbs
+            {'l', 'z'},
+            // Verb sizes
+            {10, 0},
+            // Points
+            {0, 0, 0.5, 0, 0, 0.5, -0.5, 0, 0, -0.5},
+        },
+        [](SkPath* outPath) {
+            outPath->rLineTo(0.0, 0.0);
+            outPath->rLineTo(0.5, 0.0);
+            outPath->rLineTo(0.0, 0.5);
+            outPath->rLineTo(-0.5, 0.0);
+            outPath->rLineTo(0.0, -0.5);
+            outPath->close();
+            outPath->moveTo(0.0, 0.0);
+        }
+    },
+
+    // Empty test data
+    {
+        "",
+        {
+                // Verbs
+                {},
+                {},
+                {},
+        },
+        [](SkPath* outPath) {}
     }
+
 };
 
-static TestData testData2 {
-    // Path
-    "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10",
-    {
-        // Verbs
-        {'M', 'm', 'l', 'L', 'H', 'h', 'V', 'v', 'Q', 'q', 't', 'T', 'C', 'c', 'S', 's', 'A', 'a'},
-        // VerbSizes
-        {2, 2, 2, 2, 1, 1, 1, 1, 4, 4, 2, 2, 6, 6, 4, 4, 7, 7},
-        // Points
-        {1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 7.0, 7.0, 7.0, 7.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, }
-    },
-    [](SkPath* outPath) {
-        outPath->moveTo(1.0, 1.0);
-        outPath->rMoveTo(2.0, 2.0);
-        outPath->rLineTo(3.0, 3.0);
-        outPath->lineTo(3.0, 3.0);
-        outPath->lineTo(4.0, 3.0);
-        outPath->rLineTo(4.0, 0);
-        outPath->lineTo(8.0, 5.0);
-        outPath->rLineTo(0, 5.0);
-        outPath->quadTo(6.0, 6.0, 6.0, 6.0);
-        outPath->rQuadTo(6.0, 6.0, 6.0, 6.0);
-        outPath->rQuadTo(0.0, 0.0, 7.0, 7.0);
-        outPath->quadTo(26.0, 26.0, 7.0, 7.0);
-        outPath->cubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0);
-        outPath->rCubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0);
-        outPath->cubicTo(16.0, 16.0, 9.0, 9.0, 9.0, 9.0);
-        outPath->rCubicTo(0.0, 0.0, 9.0, 9.0, 9.0, 9.0);
-        outPath->cubicTo(18.447775037328352, 20.404243860300607, 17.998389141249767, 22.8911717921705, 16.737515350332117, 24.986664170401575);
-        outPath->cubicTo(15.476641559414468, 27.08215654863265, 13.489843598291483, 28.644011882390082, 11.155893964798905, 29.37447073281729);
-        outPath->cubicTo(8.821944331306327, 30.1049295832445, 6.299226382436471, 29.954422532383525, 4.0686829203897235, 28.951642951534332);
-        outPath->cubicTo(1.838139458342976, 27.94886337068514, 0.05113662931485696, 26.161860541657013, -0.9516429515343354, 23.931317079610267);
-        outPath->cubicTo(-1.9544225323835278, 21.70077361756352, -2.1049295832444987, 19.178055668693663, -1.37447073281729, 16.844106035201087);
-        outPath->cubicTo(-0.6440118823900814, 14.51015640170851, 0.9178434513673546, 12.523358440585524, 3.0133358295984305, 11.262484649667876);
-        outPath->cubicTo(5.108828207829506, 10.001610858750228, 7.5957561396993984, 9.552224962671648, 10.000000000000005, 10.0);
-        outPath->cubicTo(10.0, 7.348852265086975, 11.054287646850167, 4.803576729418881, 12.928932188134523, 2.9289321881345254);
-        outPath->cubicTo(14.803576729418879, 1.0542876468501696, 17.348852265086972, 4.870079381441987E-16, 19.999999999999996, 0.0);
-        outPath->cubicTo(22.65114773491302, -4.870079381441987E-16, 25.19642327058112, 1.0542876468501678, 27.071067811865476, 2.9289321881345227);
-        outPath->cubicTo(28.94571235314983, 4.803576729418878, 30.0, 7.348852265086974, 30.0, 9.999999999999998);
-        outPath->cubicTo(30.0, 12.651147734913023, 28.94571235314983, 15.19642327058112, 27.071067811865476, 17.071067811865476);
-        outPath->cubicTo(25.19642327058112, 18.94571235314983, 22.651147734913028, 20.0, 20.000000000000004, 20.0);
-    }
-};
-
-static TestData testData3 {
-    // Path
-    "M5.3,13.2c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,-0.2 -0.4,-0.7 -0.2,-1.0c1.3,-1.9 2.9,-3.4 4.9,-4.5c4.1,-2.2 9.3,-2.2 13.4,0.0c1.9,1.1 3.6,2.5 4.9,4.4c0.2,0.3 0.1,0.8 -0.2,1.0c-0.3,0.2 -0.8,0.1 -1.0,-0.2c-1.2,-1.7 -2.6,-3.0 -4.3,-4.0c-3.7,-2.0 -8.3,-2.0 -12.0,0.0c-1.7,0.9 -3.2,2.3 -4.3,4.0C5.7,13.1 5.5,13.2 5.3,13.2z",
-    {
-        // Verbs
-        {'M', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'C', 'z'},
-        // Verb sizes
-        {2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0},
-        // Points
-        {5.3, 13.2, -0.1, 0, -0.3, 0, -0.4, -0.1, -0.3, -0.2, -0.4, -0.7, -0.2, -1, 1.3, -1.9, 2.9, -3.4, 4.9, -4.5, 4.1, -2.2, 9.3, -2.2, 13.4, 0, 1.9, 1.1, 3.6, 2.5, 4.9, 4.4, 0.2, 0.3, 0.1, 0.8, -0.2, 1, -0.3, 0.2, -0.8, 0.1, -1, -0.2, -1.2, -1.7, -2.6, -3, -4.3, -4, -3.7, -2, -8.3, -2, -12, 0, -1.7, 0.9, -3.2, 2.3, -4.3, 4, 5.7, 13.1, 5.5, 13.2, 5.3, 13.2},
-    },
-    [](SkPath* outPath) {
-        outPath->moveTo(5.3, 13.2);
-        outPath->rCubicTo(-0.1, 0.0, -0.3, 0.0, -0.4, -0.1);
-        outPath->rCubicTo(-0.3, -0.2, -0.4, -0.7, -0.2, -1.0);
-        outPath->rCubicTo(1.3, -1.9, 2.9, -3.4, 4.9, -4.5);
-        outPath->rCubicTo(4.1, -2.2, 9.3, -2.2, 13.4, 0.0);
-        outPath->rCubicTo(1.9, 1.1, 3.6, 2.5, 4.9, 4.4);
-        outPath->rCubicTo(0.2, 0.3, 0.1, 0.8, -0.2, 1.0);
-        outPath->rCubicTo(-0.3, 0.2, -0.8, 0.1, -1.0, -0.2);
-        outPath->rCubicTo(-1.2, -1.7, -2.6, -3.0, -4.3, -4.0);
-        outPath->rCubicTo(-3.7, -2.0, -8.3, -2.0, -12.0, 0.0);
-        outPath->rCubicTo(-1.7, 0.9, -3.2, 2.3, -4.3, 4.0);
-        outPath->cubicTo(5.7, 13.1, 5.5, 13.2, 5.3, 13.2);
-        outPath->close();
-        outPath->moveTo(5.3, 13.2);
-    }
-};
-
-static TestData testData4 {
-    // Path
-    "l0.0.0.5.0.0.5-0.5.0.0-.5z",
-    {
-        // Verbs
-        {'l', 'z'},
-        // Verb sizes
-        {10, 0},
-        // Points
-        {0, 0, 0.5, 0, 0, 0.5, -0.5, 0, 0, -0.5},
-    },
-    [](SkPath* outPath) {
-        outPath->rLineTo(0.0, 0.0);
-        outPath->rLineTo(0.5, 0.0);
-        outPath->rLineTo(0.0, 0.5);
-        outPath->rLineTo(-0.5, 0.0);
-        outPath->rLineTo(0.0, -0.5);
-        outPath->close();
-        outPath->moveTo(0.0, 0.0);
-    }
-};
-
-const static TestData testDataSet[] = {testData1, testData2, testData3, testData4};
-
-TEST(PathPaser, parseString) {
-
-    for (int i = 0; i < 4; i++) {
+TEST(PathParser, parseStringForData) {
+    for (TestData testData: sTestDataSet) {
         // Test generated path data against the given data.
         PathData pathData;
-        TestData testData = testDataSet[i];
         size_t length = strlen(testData.pathString);
         PathParser::getPathDataFromString(&pathData, testData.pathString, length);
         PathParser::dump(pathData);
         EXPECT_EQ(testData.pathData, pathData);
+    }
 
-        // Test SkPath generated
+}
+
+TEST(PathParser, createSkPathFromPathData) {
+    for (TestData testData: sTestDataSet) {
         SkPath expectedPath;
         testData.skPathLamda(&expectedPath);
         SkPath actualPath;
-        VectorDrawablePath::verbsToPath(&actualPath, &pathData);
+        VectorDrawablePath::verbsToPath(&actualPath, &testData.pathData);
         EXPECT_EQ(expectedPath, actualPath);
     }
+}
 
+TEST(PathParser, parseStringForSkPath) {
+    for (TestData testData: sTestDataSet) {
+        size_t length = strlen(testData.pathString);
+        // Check the return value as well as the SkPath generated.
+        SkPath actualPath;
+        bool hasValidData = PathParser::parseStringForSkPath(&actualPath, testData.pathString,
+                length);
+        EXPECT_EQ(hasValidData, testData.pathData.verbs.size() > 0);
+        SkPath expectedPath;
+        testData.skPathLamda(&expectedPath);
+        EXPECT_EQ(expectedPath, actualPath);
+    }
+    SkPath path;
+    EXPECT_FALSE(PathParser::parseStringForSkPath(&path, "l", 1));
+    EXPECT_FALSE(PathParser::parseStringForSkPath(&path, "1 1", 3));
+    EXPECT_FALSE(PathParser::parseStringForSkPath(&path, "LMFAO", 5));
+    EXPECT_TRUE(PathParser::parseStringForSkPath(&path, "m1 1", 4));
 }
 
 }; // namespace uirenderer
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index 28e0fd8..efa28ae 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -97,25 +97,38 @@
         return std::unique_ptr<DisplayList>(canvas.finishRecording());
     }
 
-    static sp<RenderNode> createNode(int left, int top, int right, int bottom, bool onLayer = false) {
+    typedef std::function<int(RenderProperties&)> PropSetupCallback;
+
+    static PropSetupCallback getHwLayerSetupCallback() {
+        static PropSetupCallback sLayerSetupCallback = [] (RenderProperties& properties) {
+            properties.mutateLayerProperties().setType(LayerType::RenderLayer);
+            return RenderNode::GENERIC;
+        };
+        return sLayerSetupCallback;
+    }
+
+    static sp<RenderNode> createNode(int left, int top, int right, int bottom,
+            PropSetupCallback propSetupCallback = nullptr) {
+#if HWUI_NULL_GPU
         // if RenderNodes are being sync'd/used, device info will be needed, since
         // DeviceInfo::maxTextureSize() affects layer property
         DeviceInfo::initialize();
+#endif
 
         sp<RenderNode> node = new RenderNode();
         node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
         node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        if (onLayer) {
-            node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
-            node->setPropertyFieldsDirty(RenderNode::GENERIC);
+        if (propSetupCallback) {
+            node->setPropertyFieldsDirty(propSetupCallback(node->mutateStagingProperties()));
         }
         return node;
     }
 
     template<class CanvasType>
     static sp<RenderNode> createNode(int left, int top, int right, int bottom,
-            std::function<void(CanvasType& canvas)> canvasCallback, bool onLayer = false) {
-        sp<RenderNode> node = createNode(left, top, right, bottom, onLayer);
+            std::function<void(CanvasType& canvas)> canvasCallback,
+            PropSetupCallback propSetupCallback = nullptr) {
+        sp<RenderNode> node = createNode(left, top, right, bottom, propSetupCallback);
 
         auto&& props = node->stagingProperties(); // staging, since not sync'd yet
         CanvasType canvas(props.getWidth(), props.getHeight());
diff --git a/libs/hwui/utils/Macros.h b/libs/hwui/utils/Macros.h
index 5ca9083..ccf2287 100644
--- a/libs/hwui/utils/Macros.h
+++ b/libs/hwui/utils/Macros.h
@@ -35,4 +35,7 @@
         static_assert(std::is_standard_layout<Type>::value, \
         #Type " must have standard layout")
 
+#define WARN_UNUSED_RESULT \
+    __attribute__((warn_unused_result))
+
 #endif /* MACROS_H */
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index f36d640..9ea6722 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -20,7 +20,6 @@
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
-import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
@@ -42,7 +41,6 @@
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Video;
 import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
 import android.sax.Element;
 import android.sax.ElementListener;
 import android.sax.RootElement;
@@ -326,6 +324,8 @@
     // used when scanning the image database so we know whether we have to prune
     // old thumbnail files
     private int mOriginalCount;
+    /** Whether the database had any entries in it before the scan started */
+    private boolean mWasEmptyPriorToScan = false;
     /** Whether the scanner has set a default sound for the ringer ringtone. */
     private boolean mDefaultRingtoneSet;
     /** Whether the scanner has set a default sound for the notification ringtone. */
@@ -535,18 +535,6 @@
                 if (mMtpObjectHandle != 0) {
                     entry.mRowId = 0;
                 }
-
-                if ((!mDefaultNotificationSet &&
-                        doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename))
-                        || (!mDefaultRingtoneSet &&
-                                doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename))
-                        || (!mDefaultAlarmSet &&
-                                doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename))) {
-                    Log.w(TAG, "forcing rescan of " + entry.mPath +
-                            "since ringtone setting didn't finish");
-                    scanAlways = true;
-                }
-
                 // rescan for metadata if file was modified since last scan
                 if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
                     if (noMedia) {
@@ -926,26 +914,6 @@
             }
             Uri result = null;
             boolean needToSetSettings = false;
-            // Setting a flag in order not to use bulk insert for the file related with
-            // notifications, ringtones, and alarms, because the rowId of the inserted file is
-            // needed.
-            if (notifications && !mDefaultNotificationSet) {
-                if (TextUtils.isEmpty(mDefaultNotificationFilename) ||
-                        doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) {
-                    needToSetSettings = true;
-                }
-            } else if (ringtones && !mDefaultRingtoneSet) {
-                if (TextUtils.isEmpty(mDefaultRingtoneFilename) ||
-                        doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename)) {
-                    needToSetSettings = true;
-                }
-            } else if (alarms && !mDefaultAlarmSet) {
-                if (TextUtils.isEmpty(mDefaultAlarmAlertFilename) ||
-                        doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)) {
-                    needToSetSettings = true;
-                }
-            }
-
             if (rowId == 0) {
                 if (mMtpObjectHandle != 0) {
                     values.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle);
@@ -957,6 +925,28 @@
                     }
                     values.put(Files.FileColumns.FORMAT, format);
                 }
+                // Setting a flag in order not to use bulk insert for the file related with
+                // notifications, ringtones, and alarms, because the rowId of the inserted file is
+                // needed.
+                if (mWasEmptyPriorToScan) {
+                    if (notifications && !mDefaultNotificationSet) {
+                        if (TextUtils.isEmpty(mDefaultNotificationFilename) ||
+                                doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) {
+                            needToSetSettings = true;
+                        }
+                    } else if (ringtones && !mDefaultRingtoneSet) {
+                        if (TextUtils.isEmpty(mDefaultRingtoneFilename) ||
+                                doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename)) {
+                            needToSetSettings = true;
+                        }
+                    } else if (alarms && !mDefaultAlarmSet) {
+                        if (TextUtils.isEmpty(mDefaultAlarmAlertFilename) ||
+                                doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)) {
+                            needToSetSettings = true;
+                        }
+                    }
+                }
+
                 // New file, insert it.
                 // Directories need to be inserted before the files they contain, so they
                 // get priority when bulk inserting.
@@ -1026,20 +1016,13 @@
 
         private void setSettingIfNotSet(String settingName, Uri uri, long rowId) {
 
-            if(wasSettingAlreadySet(settingName)) {
-                return;
-            }
-
             String existingSettingValue = Settings.System.getString(mContext.getContentResolver(),
                     settingName);
 
             if (TextUtils.isEmpty(existingSettingValue)) {
                 // Set the setting to the given URI
-
-                ContentResolver cr = mContext.getContentResolver();
-                Settings.System.putString(cr, settingName,
+                Settings.System.putString(mContext.getContentResolver(), settingName,
                         ContentUris.withAppendedId(uri, rowId).toString());
-                Settings.System.putInt(cr, settingSetIndicatorName(settingName), 1);
             }
         }
 
@@ -1067,20 +1050,6 @@
 
     }; // end of anonymous MediaScannerClient instance
 
-    private String settingSetIndicatorName(String base) {
-        return base + "_set";
-    }
-
-    private boolean wasSettingAlreadySet(String name) {
-        ContentResolver cr = mContext.getContentResolver();
-        String indicatorName = settingSetIndicatorName(name);
-        try {
-            return Settings.System.getInt(cr, indicatorName) != 0;
-        } catch (SettingNotFoundException e) {
-            return false;
-        }
-    }
-
     private void prescan(String filePath, boolean prescanFiles) throws RemoteException {
         Cursor c = null;
         String where = null;
@@ -1102,10 +1071,6 @@
             selectionArgs = new String[] { "" };
         }
 
-        mDefaultRingtoneSet = wasSettingAlreadySet(Settings.System.RINGTONE);
-        mDefaultNotificationSet = wasSettingAlreadySet(Settings.System.NOTIFICATION_SOUND);
-        mDefaultAlarmSet = wasSettingAlreadySet(Settings.System.ALARM_ALERT);
-
         // Tell the provider to not delete the file.
         // If the file is truly gone the delete is unnecessary, and we want to avoid
         // accidentally deleting files that are really there (this may happen if the
@@ -1124,6 +1089,7 @@
                 // with CursorWindow positioning.
                 long lastId = Long.MIN_VALUE;
                 Uri limitUri = mFilesUri.buildUpon().appendQueryParameter("limit", "1000").build();
+                mWasEmptyPriorToScan = true;
 
                 while (true) {
                     selectionArgs[0] = "" + lastId;
@@ -1142,6 +1108,7 @@
                     if (num == 0) {
                         break;
                     }
+                    mWasEmptyPriorToScan = false;
                     while (c.moveToNext()) {
                         long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
                         String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
@@ -1293,7 +1260,7 @@
         }
     }
 
-    private void postscan(final String[] directories) throws RemoteException {
+    private void postscan(String[] directories) throws RemoteException {
 
         // handle playlists last, after we know what media files are on the storage.
         if (mProcessPlaylists) {
diff --git a/packages/DefaultContainerService/AndroidManifest.xml b/packages/DefaultContainerService/AndroidManifest.xml
index e67c554..ccf1501 100644
--- a/packages/DefaultContainerService/AndroidManifest.xml
+++ b/packages/DefaultContainerService/AndroidManifest.xml
@@ -12,7 +12,9 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
 
     <application android:label="@string/service_name"
-                 android:allowBackup="false">
+                 android:allowBackup="false"
+                 android:forceDeviceEncrypted="true"
+                 android:encryptionAware="true">
 
         <service android:name=".DefaultContainerService"
                  android:enabled="true"
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java
index 49dae3d..1b5b60de 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Events.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java
@@ -117,6 +117,8 @@
         public MotionInputEvent(MotionEvent event, RecyclerView view) {
             mEvent = event;
             mView = view;
+
+            // Consider determining position lazily as an optimization.
             View child = mView.findChildViewUnder(mEvent.getX(), mEvent.getY());
             mPosition = (child != null)
                     ? mView.getChildAdapterPosition(child)
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index 9eafcc3..65e1a28 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -41,10 +41,9 @@
 import android.view.MotionEvent;
 import android.view.View;
 
-import com.android.documentsui.Events;
-import com.android.documentsui.R;
 import com.android.documentsui.Events.InputEvent;
 import com.android.documentsui.Events.MotionInputEvent;
+import com.android.documentsui.R;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -88,9 +87,7 @@
      * @param mode Selection mode
      */
     public MultiSelectManager(final RecyclerView recyclerView, int mode) {
-        this(recyclerView.getAdapter(), mode);
-
-        mEnvironment = new RuntimeSelectionEnvironment(recyclerView);
+        this(recyclerView.getAdapter(), new RuntimeSelectionEnvironment(recyclerView), mode);
 
         if (mode == MODE_MULTIPLE) {
             mBandManager = new BandController();
@@ -137,16 +134,15 @@
 
     /**
      * Constructs a new instance with {@code adapter} and {@code helper}.
+     * @param runtimeSelectionEnvironment
      * @hide
      */
     @VisibleForTesting
-    MultiSelectManager(Adapter<?> adapter, int mode) {
-        checkNotNull(adapter, "'adapter' cannot be null.");
-
+    MultiSelectManager(Adapter<?> adapter, SelectionEnvironment environment, int mode) {
+        mAdapter = checkNotNull(adapter, "'adapter' cannot be null.");
+        mEnvironment = checkNotNull(environment, "'environment' cannot be null.");
         mSingleSelect = mode == MODE_SINGLE;
 
-        mAdapter = adapter;
-
         mAdapter.registerAdapterDataObserver(
                 new AdapterDataObserver() {
 
@@ -880,7 +876,7 @@
         void focusItem(int position);
     }
 
-    /** RvFacade implementation backed by good ol' RecyclerView. */
+    /** Recycler view facade implementation backed by good ol' RecyclerView. */
     private static final class RuntimeSelectionEnvironment implements SelectionEnvironment {
 
         private final RecyclerView mView;
@@ -1960,11 +1956,50 @@
             return false;
         }
 
-        int target = RecyclerView.NO_POSITION;
+        // Here we unpack information from the event and pass it to an more
+        // easily tested method....basically eliminating the need to synthesize
+        // events and views and so on in our tests.
+        int position = findTargetPosition(view, keyCode);
+        if (position == RecyclerView.NO_POSITION) {
+            // If there is no valid navigation target, don't handle the keypress.
+            return false;
+        }
+
+        return attemptChangePosition(position, event.isShiftPressed());
+    }
+
+    @VisibleForTesting
+    boolean attemptChangePosition(int targetPosition, boolean isShiftPressed) {
+        // Focus the new file.
+        mEnvironment.focusItem(targetPosition);
+
+        if (isShiftPressed) {
+            if (!hasSelection()) {
+                // If there is no selection, start a selection when the user presses shift-arrow.
+                toggleSelection(targetPosition);
+            } else if (!mSingleSelect) {
+                mRanger.snapSelection(targetPosition);
+                notifySelectionChanged();
+            } else {
+                // We're in single select and have an existing selection.
+                // Our best guess as to what the user would expect is to advance the selection.
+                clearSelection();
+                toggleSelection(targetPosition);
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the adapter position that the key combo is targeted at.
+     */
+    private int findTargetPosition(View view, int keyCode) {
+        int position = RecyclerView.NO_POSITION;
         if (keyCode == KeyEvent.KEYCODE_MOVE_HOME) {
-            target = 0;
+            position = 0;
         } else if (keyCode == KeyEvent.KEYCODE_MOVE_END) {
-            target = mAdapter.getItemCount() - 1;
+            position = mAdapter.getItemCount() - 1;
         } else {
             // Find a navigation target based on the arrow key that the user pressed.  Ignore
             // navigation targets that aren't items in the recycler view.
@@ -1988,30 +2023,10 @@
                 // TargetView can be null, for example, if the user pressed <down> at the bottom of
                 // the list.
                 if (targetView != null) {
-                    target = mEnvironment.getAdapterPositionForChildView(targetView);
+                    position = mEnvironment.getAdapterPositionForChildView(targetView);
                 }
             }
         }
-
-        if (target == RecyclerView.NO_POSITION) {
-            // If there is no valid navigation target, don't handle the keypress.
-            return false;
-        }
-
-        // Focus the new file.
-        mEnvironment.focusItem(target);
-
-        if (event.isShiftPressed()) {
-            if (!hasSelection()) {
-                // If there is no selection, start a selection when the user presses shift-arrow.
-                toggleSelection(mEnvironment.getAdapterPositionForChildView(view));
-            }
-
-            mRanger.snapSelection(target);
-            notifySelectionChanged();
-        }
-
-        return true;
+        return position;
     }
-
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index 24f5c9e..d1ce564 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -23,7 +23,6 @@
 import android.view.ViewGroup;
 
 import com.android.documentsui.TestInputEvent;
-import com.android.documentsui.dirlist.MultiSelectManager;
 import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 
 import org.mockito.Mockito;
@@ -49,11 +48,13 @@
     private MultiSelectManager mManager;
     private TestAdapter mAdapter;
     private TestCallback mCallback;
+    private TestSelectionEnvironment mEnv;
 
     public void setUp() throws Exception {
         mAdapter = new TestAdapter(items);
         mCallback = new TestCallback();
-        mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE);
+        mEnv = new TestSelectionEnvironment();
+        mManager = new MultiSelectManager(mAdapter, mEnv, MultiSelectManager.MODE_MULTIPLE);
         mManager.addCallback(mCallback);
     }
 
@@ -171,7 +172,7 @@
     }
 
     public void testSingleSelectMode() {
-        mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
+        mManager = new MultiSelectManager(mAdapter, mEnv, MultiSelectManager.MODE_SINGLE);
         mManager.addCallback(mCallback);
         longPress(20);
         tap(13);
@@ -179,13 +180,21 @@
     }
 
     public void testSingleSelectMode_ShiftTap() {
-        mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
+        mManager = new MultiSelectManager(mAdapter, mEnv, MultiSelectManager.MODE_SINGLE);
         mManager.addCallback(mCallback);
         longPress(13);
         shiftTap(20);
         assertSelection(20);
     }
 
+    public void testSingleSelectMode_ShiftDoesNotExtendSelection() {
+        mManager = new MultiSelectManager(mAdapter, mEnv, MultiSelectManager.MODE_SINGLE);
+        mManager.addCallback(mCallback);
+        longPress(20);
+        keyToPosition(22, true);
+        assertSelection(22);
+    }
+
     public void testProvisionalSelection() {
         Selection s = mManager.getSelection();
         assertSelection();
@@ -235,6 +244,10 @@
         mManager.onSingleTapUp(TestInputEvent.shiftClick(position));
     }
 
+    private void keyToPosition(int position, boolean shift) {
+        mManager.attemptChangePosition(position, shift);
+    }
+
     private void assertSelected(int... expected) {
         for (int i = 0; i < expected.length; i++) {
             Selection selection = mManager.getSelection();
@@ -290,11 +303,8 @@
 
     private static final class TestHolder extends RecyclerView.ViewHolder {
         // each data item is just a string in this case
-        public View view;
-        public String string;
         public TestHolder(View view) {
             super(view);
-            this.view = view;
         }
     }
 
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
new file mode 100644
index 0000000..b4324a8
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
@@ -0,0 +1,108 @@
+/*
+ * 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.dirlist;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.v7.widget.RecyclerView.OnScrollListener;
+import android.view.View;
+
+import com.android.documentsui.dirlist.MultiSelectManager.SelectionEnvironment;
+
+public class TestSelectionEnvironment implements SelectionEnvironment {
+
+    @Override
+    public void showBand(Rect rect) {
+    }
+
+    @Override
+    public void hideBand() {
+    }
+
+    @Override
+    public void addOnScrollListener(OnScrollListener listener) {
+    }
+
+    @Override
+    public void removeOnScrollListener(OnScrollListener listener) {
+    }
+
+    @Override
+    public void scrollBy(int dy) {
+    }
+
+    @Override
+    public int getHeight() {
+        return 0;
+    }
+
+    @Override
+    public void invalidateView() {
+    }
+
+    @Override
+    public void runAtNextFrame(Runnable r) {
+    }
+
+    @Override
+    public void removeCallback(Runnable r) {
+    }
+
+    @Override
+    public Point createAbsolutePoint(Point relativePoint) {
+        return null;
+    }
+
+    @Override
+    public Rect getAbsoluteRectForChildViewAt(int index) {
+        return null;
+    }
+
+    @Override
+    public int getAdapterPositionAt(int index) {
+        return 0;
+    }
+
+    @Override
+    public int getAdapterPositionForChildView(View view) {
+        return 0;
+    }
+
+    @Override
+    public int getColumnCount() {
+        return 0;
+    }
+
+    @Override
+    public int getRowCount() {
+        return 0;
+    }
+
+    @Override
+    public int getChildCount() {
+        return 0;
+    }
+
+    @Override
+    public int getVisibleChildCount() {
+        return 0;
+    }
+
+    @Override
+    public void focusItem(int position) {
+    }
+}
diff --git a/packages/FakeOemFeatures/AndroidManifest.xml b/packages/FakeOemFeatures/AndroidManifest.xml
index 93b8b47..fe74ad8 100644
--- a/packages/FakeOemFeatures/AndroidManifest.xml
+++ b/packages/FakeOemFeatures/AndroidManifest.xml
@@ -11,7 +11,9 @@
         android:allowClearUserData="false"
         android:allowBackup="false"
         android:hardwareAccelerated="true"
-        android:label="Fake OEM Features">
+        android:label="Fake OEM Features"
+        android:forceDeviceEncrypted="true"
+        android:encryptionAware="true">
 
         <service android:name=".FakeCoreService" android:process=":core"
                 android:label="Fake OEM Core Service" />
diff --git a/packages/FusedLocation/AndroidManifest.xml b/packages/FusedLocation/AndroidManifest.xml
index 6a4d4bf..ed84c0d 100644
--- a/packages/FusedLocation/AndroidManifest.xml
+++ b/packages/FusedLocation/AndroidManifest.xml
@@ -28,7 +28,9 @@
 
     <application
             android:label="@string/app_label"
-            android:process="system">
+            android:process="system"
+            android:forceDeviceEncrypted="true"
+            android:encryptionAware="true">
 
         <uses-library android:name="com.android.location.provider" />
 
@@ -37,7 +39,7 @@
              version. -->
         <service android:name="com.android.location.fused.FusedLocationService"
                  android:exported="true"
-                 android:permission="android.permission.WRITE_SECURE_SETTINGS" >
+                 android:permission="android.permission.WRITE_SECURE_SETTINGS">
            <intent-filter>
                <action android:name="com.android.location.service.FusedLocationProvider" />
            </intent-filter>
diff --git a/packages/InputDevices/AndroidManifest.xml b/packages/InputDevices/AndroidManifest.xml
index f0e4abc..07885ea 100644
--- a/packages/InputDevices/AndroidManifest.xml
+++ b/packages/InputDevices/AndroidManifest.xml
@@ -6,7 +6,9 @@
     <application
             android:allowClearUserData="false"
             android:label="@string/app_label"
-            android:process="system">
+            android:process="system"
+            android:forceDeviceEncrypted="true"
+            android:encryptionAware="true">
 
         <receiver android:name=".InputDeviceReceiver"
                 android:label="@string/keyboard_layouts_label">
diff --git a/packages/Keyguard/AndroidManifest.xml b/packages/Keyguard/AndroidManifest.xml
index e19246c..54972b4 100644
--- a/packages/Keyguard/AndroidManifest.xml
+++ b/packages/Keyguard/AndroidManifest.xml
@@ -45,7 +45,9 @@
     <application android:label="@string/app_name"
         android:process="com.android.systemui"
         android:persistent="true"
-        android:supportsRtl="true">
+        android:supportsRtl="true"
+        android:forceDeviceEncrypted="true"
+        android:encryptionAware="true">
 
     </application>
 </manifest>
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
index f95b0ae..9d1df26 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
@@ -18,7 +18,6 @@
 
 import android.app.ActivityManager;
 import android.app.AlarmManager;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -198,12 +197,18 @@
     }
 
     private String getOwnerInfo() {
-        ContentResolver res = getContext().getContentResolver();
         String info = null;
-        final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
-                KeyguardUpdateMonitor.getCurrentUser());
-        if (ownerInfoEnabled) {
-            info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
+        if (mLockPatternUtils.isDeviceOwnerInfoEnabled()) {
+            // Use the device owner information set by device policy client via
+            // device policy manager.
+            info = mLockPatternUtils.getDeviceOwnerInfo();
+        } else {
+            // Use the current user owner information if enabled.
+            final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
+                    KeyguardUpdateMonitor.getCurrentUser());
+            if (ownerInfoEnabled) {
+                info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
+            }
         }
         return info;
     }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 0f31e2c..e3be534 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -16,22 +16,20 @@
 
 package com.android.mtp;
 
+import static com.android.mtp.MtpDatabaseConstants.*;
+
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQueryBuilder;
 import android.mtp.MtpObjectInfo;
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Root;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.Objects;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Database for MTP objects.
@@ -46,182 +44,76 @@
  * remembers the map of document ID and object handle, and remaps new object handle with document ID
  * by comparing the directory structure and object name.
  *
+ * To start putting documents into the database, the client needs to call
+ * {@link #startAddingChildDocuments(String)} with the parent document ID. Also it needs to call
+ * {@link #stopAddingChildDocuments(String)} after putting all child documents to the database.
+ * (All explanations are same for root documents)
+ *
+ * database.startAddingChildDocuments();
+ * database.putChildDocuments();
+ * database.stopAddingChildDocuments();
+ *
+ * To update the existing documents, the client code can repeat to call the three methods again.
+ * The newly added rows update corresponding existing rows that have same MTP identifier like
+ * objectHandle.
+ *
+ * The client can call putChildDocuments multiple times to add documents by chunk, but it needs to
+ * put all documents under the parent before calling stopAddingChildDocuments. Otherwise missing
+ * documents are regarded as deleted, and will be removed from the database.
+ *
+ * If the client calls clearMtpIdentifier(), it clears MTP identifier in the database. In this case,
+ * the database tries to find corresponding rows by using document's name instead of MTP identifier
+ * at the next update cycle.
+ *
  * TODO: Remove @VisibleForTesting annotation when we start to use this class.
  * TODO: Improve performance by SQL optimization.
  */
 @VisibleForTesting
 class MtpDatabase {
-    private static final int VERSION = 1;
-    private static final String NAME = "mtp";
-
-    /**
-     * Table representing documents including root documents.
-     */
-    private static final String TABLE_DOCUMENTS = "Documents";
-
-    /**
-     * Table containing additional information only available for root documents.
-     * The table uses same primary keys with corresponding documents.
-     */
-    private static final String TABLE_ROOT_EXTRA = "RootExtra";
-
-    /**
-     * View to join Documents and RootExtra tables to provide roots information.
-     */
-    private static final String VIEW_ROOTS = "Roots";
-
-    static final String COLUMN_DEVICE_ID = "device_id";
-    static final String COLUMN_STORAGE_ID = "storage_id";
-    static final String COLUMN_OBJECT_HANDLE = "object_handle";
-    static final String COLUMN_PARENT_DOCUMENT_ID = "parent_document_id";
-    static final String COLUMN_ROW_STATE = "row_state";
-
-    /**
-     * The state represents that the row has a valid object handle.
-     */
-    static final int ROW_STATE_MAPPED = 0;
-
-    /**
-     * The state represents that the object handle was cleared because the MTP session closed.
-     * External application can still fetch the unmapped documents. If the external application
-     * tries to open an unmapped document, the provider resolves the document with new object handle
-     * ahead.
-     */
-    static final int ROW_STATE_UNMAPPED = 1;
-
-    /**
-     * The state represents the raw has a valid object handle but it may be going to be merged into
-     * another unmapped row. After fetching all documents under the parent, the database tries to
-     * map the mapping document and the unmapped document in order to keep old document ID alive.
-     */
-    static final int ROW_STATE_MAPPING = 2;
-
-    private static final String SELECTION_DOCUMENT_ID = Document.COLUMN_DOCUMENT_ID + " = ?";
-    private static final String SELECTION_ROOT_ID = Root.COLUMN_ROOT_ID + " = ?";
-    private static final String SELECTION_ROOT_DOCUMENTS =
-            COLUMN_DEVICE_ID + " = ? AND " + COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
-    private static final String SELECTION_CHILD_DOCUMENTS = COLUMN_PARENT_DOCUMENT_ID + " = ?";
-
-    static class ParentNotFoundException extends Exception {}
-
-    private static class OpenHelper extends SQLiteOpenHelper {
-        private static final String QUERY_CREATE_DOCUMENTS =
-                "CREATE TABLE " + TABLE_DOCUMENTS + " (" +
-                Document.COLUMN_DOCUMENT_ID +
-                    " INTEGER PRIMARY KEY AUTOINCREMENT," +
-                COLUMN_DEVICE_ID + " INTEGER NOT NULL," +
-                COLUMN_STORAGE_ID + " INTEGER," +
-                COLUMN_OBJECT_HANDLE + " INTEGER," +
-                COLUMN_PARENT_DOCUMENT_ID + " INTEGER," +
-                COLUMN_ROW_STATE + " INTEGER NOT NULL," +
-                Document.COLUMN_MIME_TYPE + " TEXT," +
-                Document.COLUMN_DISPLAY_NAME + " TEXT NOT NULL," +
-                Document.COLUMN_SUMMARY + " TEXT," +
-                Document.COLUMN_LAST_MODIFIED + " INTEGER," +
-                Document.COLUMN_ICON + " INTEGER," +
-                Document.COLUMN_FLAGS + " INTEGER NOT NULL," +
-                Document.COLUMN_SIZE + " INTEGER NOT NULL);";
-
-        private static final String QUERY_CREATE_ROOT_EXTRA =
-                "CREATE TABLE " + TABLE_ROOT_EXTRA + " (" +
-                Root.COLUMN_ROOT_ID + " INTEGER PRIMARY KEY," +
-                Root.COLUMN_FLAGS + " INTEGER NOT NULL," +
-                Root.COLUMN_AVAILABLE_BYTES + " INTEGER NOT NULL," +
-                Root.COLUMN_CAPACITY_BYTES + " INTEGER NOT NULL," +
-                Root.COLUMN_MIME_TYPES + " TEXT NOT NULL);";
-
-        /**
-         * Creates a view to join Documents table and RootExtra table on their primary keys to
-         * provide DocumentContract.Root equivalent information.
-         */
-        private static final String QUERY_CREATE_VIEW_ROOTS =
-                "CREATE VIEW " + VIEW_ROOTS + " AS SELECT " +
-                        TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " +
-                                Root.COLUMN_ROOT_ID + "," +
-                        TABLE_ROOT_EXTRA + "." + Root.COLUMN_FLAGS + "," +
-                        TABLE_DOCUMENTS + "." + Document.COLUMN_ICON + " AS " +
-                                Root.COLUMN_ICON + "," +
-                        TABLE_DOCUMENTS + "." + Document.COLUMN_DISPLAY_NAME + " AS " +
-                                Root.COLUMN_TITLE + "," +
-                        TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY + " AS " +
-                                Root.COLUMN_SUMMARY + "," +
-                        TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " +
-                        Root.COLUMN_DOCUMENT_ID + "," +
-                        TABLE_ROOT_EXTRA + "." + Root.COLUMN_AVAILABLE_BYTES + "," +
-                        TABLE_ROOT_EXTRA + "." + Root.COLUMN_CAPACITY_BYTES + "," +
-                        TABLE_ROOT_EXTRA + "." + Root.COLUMN_MIME_TYPES + "," +
-                        TABLE_DOCUMENTS + "." + COLUMN_ROW_STATE +
-                " FROM " + TABLE_DOCUMENTS + " INNER JOIN " + TABLE_ROOT_EXTRA +
-                " ON " +
-                        COLUMN_PARENT_DOCUMENT_ID + " IS NULL AND " +
-                        TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID +
-                        "=" +
-                        TABLE_ROOT_EXTRA + "." + Root.COLUMN_ROOT_ID;
-
-        public OpenHelper(Context context) {
-            super(context, NAME, null, VERSION);
-        }
-
-        @Override
-        public void onCreate(SQLiteDatabase db) {
-            db.execSQL(QUERY_CREATE_DOCUMENTS);
-            db.execSQL(QUERY_CREATE_ROOT_EXTRA);
-            db.execSQL(QUERY_CREATE_VIEW_ROOTS);
-        }
-
-        @Override
-        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    private final SQLiteDatabase mDatabase;
+    private final MtpDatabaseInternal mDatabase;
+    private final Map<String, Integer> mMappingMode = new HashMap<>();
 
     @VisibleForTesting
     MtpDatabase(Context context) {
-        final OpenHelper helper = new OpenHelper(context);
-        mDatabase = helper.getWritableDatabase();
-    }
-
-    @VisibleForTesting
-    static void deleteDatabase(Context context) {
-        SQLiteDatabase.deleteDatabase(context.getDatabasePath(NAME));
+        mDatabase = new MtpDatabaseInternal(context);
     }
 
     @VisibleForTesting
     Cursor queryRoots(String[] columnNames) {
-        return mDatabase.query(
-                VIEW_ROOTS,
-                columnNames,
-                COLUMN_ROW_STATE + " IN (?, ?)",
-                strings(ROW_STATE_MAPPED, ROW_STATE_UNMAPPED),
-                null,
-                null,
-                null);
+        return mDatabase.queryRoots(columnNames);
     }
 
     @VisibleForTesting
     Cursor queryRootDocuments(String[] columnNames) {
-        return mDatabase.query(
-                TABLE_DOCUMENTS,
-                columnNames,
-                COLUMN_ROW_STATE + " IN (?, ?)",
-                strings(ROW_STATE_MAPPED, ROW_STATE_UNMAPPED),
-                null,
-                null,
-                null);
+        return mDatabase.queryRootDocuments(columnNames);
     }
 
     @VisibleForTesting
     Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
-        return mDatabase.query(
-                TABLE_DOCUMENTS,
-                columnNames,
-                COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_PARENT_DOCUMENT_ID + " = ?",
-                strings(ROW_STATE_MAPPED, ROW_STATE_UNMAPPED, parentDocumentId),
-                null,
-                null,
-                null);
+        return mDatabase.queryChildDocuments(columnNames, parentDocumentId);
+    }
+
+    @VisibleForTesting
+    void startAddingRootDocuments(int deviceId) {
+        final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId);
+        if (mMappingMode.containsKey(mappingStateKey)) {
+            throw new Error("Mapping for the root has already started.");
+        }
+        mMappingMode.put(
+                mappingStateKey,
+                mDatabase.startAddingDocuments(
+                        SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId)));
+    }
+
+    @VisibleForTesting
+    void startAddingChildDocuments(String parentDocumentId) {
+        final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId);
+        if (mMappingMode.containsKey(mappingStateKey)) {
+            throw new Error("Mapping for the root has already started.");
+        }
+        mMappingMode.put(
+                mappingStateKey,
+                mDatabase.startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId));
     }
 
     @VisibleForTesting
@@ -236,20 +128,37 @@
                 valuesList[i] = new ContentValues();
                 getRootDocumentValues(valuesList[i], resources, roots[i]);
             }
-            final long[] documentIds =
-                    putDocuments(valuesList, SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId));
+            boolean heuristic;
+            String mapColumn;
+            switch (mMappingMode.get(getRootDocumentsMappingStateKey(deviceId))) {
+                case MAP_BY_MTP_IDENTIFIER:
+                    heuristic = false;
+                    mapColumn = COLUMN_STORAGE_ID;
+                    break;
+                case MAP_BY_NAME:
+                    heuristic = true;
+                    mapColumn = Document.COLUMN_DISPLAY_NAME;
+                    break;
+                default:
+                    throw new Error("Unexpected map mode.");
+            }
+            final long[] documentIds = mDatabase.putDocuments(
+                    valuesList,
+                    SELECTION_ROOT_DOCUMENTS,
+                    Integer.toString(deviceId),
+                    heuristic,
+                    mapColumn);
             final ContentValues values = new ContentValues();
             int i = 0;
             for (final MtpRoot root : roots) {
                 // Use the same value for the root ID and the corresponding document ID.
                 values.put(Root.COLUMN_ROOT_ID, documentIds[i++]);
-                values.put(Root.COLUMN_FLAGS,
-                        Root.FLAG_SUPPORTS_IS_CHILD |
-                        Root.FLAG_SUPPORTS_CREATE);
+                values.put(
+                        Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
                 values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace);
                 values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity);
                 values.put(Root.COLUMN_MIME_TYPES, "");
-                mDatabase.insert(TABLE_ROOT_EXTRA, null, values);
+                mDatabase.putRootExtra(values);
             }
             mDatabase.setTransactionSuccessful();
         } finally {
@@ -264,172 +173,72 @@
             valuesList[i] = new ContentValues();
             getChildDocumentValues(valuesList[i], deviceId, parentId, documents[i]);
         }
-        putDocuments(valuesList, SELECTION_CHILD_DOCUMENTS, parentId);
-    }
-
-    /**
-     * Clears MTP related identifier.
-     * It clears MTP's object handle and storage ID that are not stable over MTP sessions and mark
-     * the all documents as 'unmapped'. It also remove 'mapping' rows as mapping is cancelled now.
-     */
-    @VisibleForTesting
-    void clearMapping() {
-        mDatabase.beginTransaction();
-        try {
-            deleteDocumentsAndRoots(COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_MAPPING));
-            final ContentValues values = new ContentValues();
-            values.putNull(COLUMN_OBJECT_HANDLE);
-            values.putNull(COLUMN_STORAGE_ID);
-            values.put(COLUMN_ROW_STATE, ROW_STATE_UNMAPPED);
-            mDatabase.update(TABLE_DOCUMENTS, values, null, null);
-            mDatabase.setTransactionSuccessful();
-        } finally {
-            mDatabase.endTransaction();
+        boolean heuristic;
+        String mapColumn;
+        switch (mMappingMode.get(getChildDocumentsMappingStateKey(parentId))) {
+            case MAP_BY_MTP_IDENTIFIER:
+                heuristic = false;
+                mapColumn = COLUMN_STORAGE_ID;
+                break;
+            case MAP_BY_NAME:
+                heuristic = true;
+                mapColumn = Document.COLUMN_DISPLAY_NAME;
+                break;
+            default:
+                throw new Error("Unexpected map mode.");
         }
+        mDatabase.putDocuments(
+                valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn);
     }
 
     @VisibleForTesting
-    void resolveRootDocuments(int deviceId) {
-        resolveDocuments(SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId));
+    void clearMapping() {
+        mDatabase.clearMapping();
+        mMappingMode.clear();
     }
 
     @VisibleForTesting
-    void resolveChildDocuments(String parentId) {
-        resolveDocuments(SELECTION_CHILD_DOCUMENTS, parentId);
+    void stopAddingRootDocuments(int deviceId) {
+        final String mappingModeKey = getRootDocumentsMappingStateKey(deviceId);
+        switch (mMappingMode.get(mappingModeKey)) {
+            case MAP_BY_MTP_IDENTIFIER:
+                mDatabase.stopAddingDocuments(
+                        SELECTION_ROOT_DOCUMENTS,
+                        Integer.toString(deviceId),
+                        COLUMN_STORAGE_ID);
+                break;
+            case MAP_BY_NAME:
+                mDatabase.stopAddingDocuments(
+                        SELECTION_ROOT_DOCUMENTS,
+                        Integer.toString(deviceId),
+                        Document.COLUMN_DISPLAY_NAME);
+                break;
+            default:
+                throw new Error("Unexpected mapping state.");
+        }
+        mMappingMode.remove(mappingModeKey);
     }
 
-    /**
-     * Puts the documents into the database.
-     * If the database found another unmapped document that shares the same name and parent,
-     * the document may be merged into the unmapped document. In that case, the database marks the
-     * root as 'mapping' and wait for {@link #resolveRootDocuments(int)} is invoked.
-     * @param valuesList Values that are stored in the database.
-     * @param selection SQL where closure to select rows that shares the same parent.
-     * @param arg Argument for selection SQL.
-     * @return List of Document ID inserted to the table.
-     */
-    private long[] putDocuments(ContentValues[] valuesList, String selection, String arg) {
-        mDatabase.beginTransaction();
-        try {
-            final long[] documentIds = new long[valuesList.length];
-            int i = 0;
-            for (final ContentValues values : valuesList) {
-                final String displayName =
-                        values.getAsString(Document.COLUMN_DISPLAY_NAME);
-                final long numUnmapped = DatabaseUtils.queryNumEntries(
-                        mDatabase,
-                        TABLE_DOCUMENTS,
-                        selection + " AND " +
-                        COLUMN_ROW_STATE + " = ? AND " +
-                        Document.COLUMN_DISPLAY_NAME + " = ?",
-                        strings(arg, ROW_STATE_UNMAPPED, displayName));
-                if (numUnmapped != 0) {
-                    values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPING);
-                }
-                // Document ID is a primary integer key of the table. So the returned row IDs should
-                // be same with the document ID.
-                documentIds[i++] = mDatabase.insert(TABLE_DOCUMENTS, null, values);
-            }
-
-            mDatabase.setTransactionSuccessful();
-            return documentIds;
-        } finally {
-            mDatabase.endTransaction();
+    @VisibleForTesting
+    void stopAddingChildDocuments(String parentId) {
+        final String mappingModeKey = getChildDocumentsMappingStateKey(parentId);
+        switch (mMappingMode.get(mappingModeKey)) {
+            case MAP_BY_MTP_IDENTIFIER:
+                mDatabase.stopAddingDocuments(
+                        SELECTION_CHILD_DOCUMENTS,
+                        parentId,
+                        COLUMN_OBJECT_HANDLE);
+                break;
+            case MAP_BY_NAME:
+                mDatabase.stopAddingDocuments(
+                        SELECTION_CHILD_DOCUMENTS,
+                        parentId,
+                        Document.COLUMN_DISPLAY_NAME);
+                break;
+            default:
+                throw new Error("Unexpected mapping state.");
         }
-    }
-
-    /**
-     * Maps 'unmapped' document and 'mapping' document that don't have document but shares the same
-     * name.
-     * If the database does not find corresponding 'mapping' document, it just removes 'unmapped'
-     * document from the database.
-     * @param selection Query to select rows for resolving.
-     * @param arg Argument for selection SQL.
-     */
-    private void resolveDocuments(String selection, String arg) {
-        mDatabase.beginTransaction();
-        try {
-            // Get 1-to-1 mapping of unmapped document and mapping document.
-            final String unmappedIdQuery = createStateFilter(
-                    ROW_STATE_UNMAPPED, Document.COLUMN_DOCUMENT_ID);
-            final String mappingIdQuery = createStateFilter(
-                    ROW_STATE_MAPPING, Document.COLUMN_DOCUMENT_ID);
-            // SQL should be like:
-            // SELECT group_concat(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END),
-            //        group_concat(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END)
-            // WHERE device_id = ? AND parent_document_id IS NULL
-            // GROUP BY display_name
-            // HAVING count(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END) = 1 AND
-            //        count(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END) = 1
-            final Cursor mergingCursor = mDatabase.query(
-                    TABLE_DOCUMENTS,
-                    new String[] {
-                            "group_concat(" + unmappedIdQuery + ")",
-                            "group_concat(" + mappingIdQuery + ")"
-                    },
-                    selection,
-                    strings(arg),
-                    Document.COLUMN_DISPLAY_NAME,
-                    "count(" + unmappedIdQuery + ") = 1 AND count(" + mappingIdQuery + ") = 1",
-                    null);
-
-            final ContentValues values = new ContentValues();
-            while (mergingCursor.moveToNext()) {
-                final String unmappedId = mergingCursor.getString(0);
-                final String mappingId = mergingCursor.getString(1);
-
-                // Obtain the new values including the latest object handle from mapping row.
-                getFirstRow(
-                        TABLE_DOCUMENTS,
-                        SELECTION_DOCUMENT_ID,
-                        new String[] { mappingId },
-                        values);
-                values.remove(Document.COLUMN_DOCUMENT_ID);
-                values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPED);
-                mDatabase.update(
-                        TABLE_DOCUMENTS,
-                        values,
-                        SELECTION_DOCUMENT_ID,
-                        new String[] { unmappedId });
-
-                getFirstRow(
-                        TABLE_ROOT_EXTRA,
-                        SELECTION_ROOT_ID,
-                        new String[] { mappingId },
-                        values);
-                if (values.size() > 0) {
-                    values.remove(Root.COLUMN_ROOT_ID);
-                    mDatabase.update(
-                            TABLE_ROOT_EXTRA,
-                            values,
-                            SELECTION_ROOT_ID,
-                            new String[] { unmappedId });
-                }
-
-                // Delete 'mapping' row.
-                deleteDocumentsAndRoots(SELECTION_DOCUMENT_ID, new String[] { mappingId });
-            }
-            mergingCursor.close();
-
-            // Delete all unmapped rows that cannot be mapped.
-            deleteDocumentsAndRoots(
-                    COLUMN_ROW_STATE + " = ? AND " + selection,
-                    strings(ROW_STATE_UNMAPPED, arg));
-
-            // The database cannot find old document ID for the mapping rows.
-            // Turn the all mapping rows into mapped state, which means the rows become to be
-            // valid with new document ID.
-            values.clear();
-            values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPED);
-            mDatabase.update(
-                    TABLE_DOCUMENTS,
-                    values,
-                    COLUMN_ROW_STATE + " = ? AND " + selection,
-                    strings(ROW_STATE_MAPPING, arg));
-            mDatabase.setTransactionSuccessful();
-        } finally {
-            mDatabase.endTransaction();
-        }
+        mMappingMode.remove(mappingModeKey);
     }
 
     /**
@@ -445,7 +254,7 @@
         values.put(COLUMN_STORAGE_ID, root.mStorageId);
         values.putNull(COLUMN_OBJECT_HANDLE);
         values.putNull(COLUMN_PARENT_DOCUMENT_ID);
-        values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPED);
+        values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
         values.put(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
         values.put(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources));
         values.putNull(Document.COLUMN_SUMMARY);
@@ -482,7 +291,7 @@
         values.put(COLUMN_STORAGE_ID, info.getStorageId());
         values.put(COLUMN_OBJECT_HANDLE, info.getObjectHandle());
         values.put(COLUMN_PARENT_DOCUMENT_ID, parentId);
-        values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPED);
+        values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
         values.put(Document.COLUMN_MIME_TYPE, mimeType);
         values.put(Document.COLUMN_DISPLAY_NAME, info.getName());
         values.putNull(Document.COLUMN_SUMMARY);
@@ -494,73 +303,11 @@
         values.put(Document.COLUMN_SIZE, info.getCompressedSize());
     }
 
-    /**
-     * Obtains values of the first row for the query.
-     * @param values ContentValues that the values are stored to.
-     * @param table Target table.
-     * @param selection Query to select rows.
-     * @param args Argument for query.
-     */
-    private void getFirstRow(String table, String selection, String[] args, ContentValues values) {
-        values.clear();
-        final Cursor cursor = mDatabase.query(table, null, selection, args, null, null, null, "1");
-        if (cursor.getCount() == 0) {
-            return;
-        }
-        cursor.moveToNext();
-        DatabaseUtils.cursorRowToContentValues(cursor, values);
-        cursor.close();
+    private String getRootDocumentsMappingStateKey(int deviceId) {
+        return "RootDocuments/" + deviceId;
     }
 
-    /**
-     * Deletes a document, and its root information if the document is a root document.
-     * @param selection Query to select documents.
-     * @param args Arguments for selection.
-     */
-    private void deleteDocumentsAndRoots(String selection, String[] args) {
-        mDatabase.beginTransaction();
-        try {
-            mDatabase.delete(
-                    TABLE_ROOT_EXTRA,
-                    Root.COLUMN_ROOT_ID + " IN (" + SQLiteQueryBuilder.buildQueryString(
-                            false,
-                            TABLE_DOCUMENTS,
-                            new String[] { Document.COLUMN_DOCUMENT_ID },
-                            selection,
-                            null,
-                            null,
-                            null,
-                            null) + ")",
-                    args);
-            mDatabase.delete(TABLE_DOCUMENTS, selection, args);
-            mDatabase.setTransactionSuccessful();
-        } finally {
-            mDatabase.endTransaction();
-        }
-    }
-
-    /**
-     * Converts values into string array.
-     * @param args Values converted into string array.
-     * @return String array.
-     */
-    private static String[] strings(Object... args) {
-        final String[] results = new String[args.length];
-        for (int i = 0; i < args.length; i++) {
-            results[i] = Objects.toString(args[i]);
-        }
-        return results;
-    }
-
-    /**
-     * Gets SQL expression that represents the given value or NULL depends on the row state.
-     * @param state Expected row state.
-     * @param a SQL value.
-     * @return Expression that represents a if the row state is expected one, and represents NULL
-     *     otherwise.
-     */
-    private static String createStateFilter(int state, String a) {
-        return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
-                " THEN " + a + " ELSE NULL END";
+    private String getChildDocumentsMappingStateKey(String parentDocumentId) {
+        return "ChildDocuments/" + parentDocumentId;
     }
 }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
new file mode 100644
index 0000000..5fb16ec
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
@@ -0,0 +1,141 @@
+/*
+ * 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.mtp;
+
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+
+/**
+ * Class containing MtpDatabase constants.
+ */
+class MtpDatabaseConstants {
+    static final int DATABASE_VERSION = 1;
+    static final String DATABASE_NAME = null;
+
+    /**
+     * Table representing documents including root documents.
+     */
+    static final String TABLE_DOCUMENTS = "Documents";
+
+    /**
+     * Table containing additional information only available for root documents.
+     * The table uses same primary keys with corresponding documents.
+     */
+    static final String TABLE_ROOT_EXTRA = "RootExtra";
+
+    /**
+     * View to join Documents and RootExtra tables to provide roots information.
+     */
+    static final String VIEW_ROOTS = "Roots";
+
+    static final String COLUMN_DEVICE_ID = "device_id";
+    static final String COLUMN_STORAGE_ID = "storage_id";
+    static final String COLUMN_OBJECT_HANDLE = "object_handle";
+    static final String COLUMN_PARENT_DOCUMENT_ID = "parent_document_id";
+    static final String COLUMN_ROW_STATE = "row_state";
+
+    /**
+     * The state represents that the row has a valid object handle.
+     */
+    static final int ROW_STATE_VALID = 0;
+
+    /**
+     * The state represents that the rows added at the previous cycle and need to be updated with
+     * fresh values.
+     * The row may not have valid object handle. External application can still fetch the documents.
+     * If the external application tries to fetch object handle, the provider resolves pending
+     * documents with invalidated documents ahead.
+     */
+    static final int ROW_STATE_INVALIDATED = 1;
+
+    /**
+     * The state represents the raw has a valid object handle but it may be going to be mapped with
+     * another rows invalidated. After fetching all documents under the parent, the database tries
+     * to map the pending documents and the invalidated documents in order to keep old document ID
+     * alive.
+     */
+    static final int ROW_STATE_PENDING = 2;
+
+    /**
+     * Mapping mode that uses MTP identifier to find corresponding rows.
+     */
+    static final int MAP_BY_MTP_IDENTIFIER = 0;
+
+    /**
+     * Mapping mode that uses name to find corresponding rows.
+     */
+    static final int MAP_BY_NAME = 1;
+
+    static final String SELECTION_DOCUMENT_ID = Document.COLUMN_DOCUMENT_ID + " = ?";
+    static final String SELECTION_ROOT_ID = Root.COLUMN_ROOT_ID + " = ?";
+    static final String SELECTION_ROOT_DOCUMENTS =
+            COLUMN_DEVICE_ID + " = ? AND " + COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
+    static final String SELECTION_CHILD_DOCUMENTS = COLUMN_PARENT_DOCUMENT_ID + " = ?";
+
+    static final String QUERY_CREATE_DOCUMENTS =
+            "CREATE TABLE " + TABLE_DOCUMENTS + " (" +
+            Document.COLUMN_DOCUMENT_ID +
+                " INTEGER PRIMARY KEY AUTOINCREMENT," +
+            COLUMN_DEVICE_ID + " INTEGER NOT NULL," +
+            COLUMN_STORAGE_ID + " INTEGER," +
+            COLUMN_OBJECT_HANDLE + " INTEGER," +
+            COLUMN_PARENT_DOCUMENT_ID + " INTEGER," +
+            COLUMN_ROW_STATE + " INTEGER NOT NULL," +
+            Document.COLUMN_MIME_TYPE + " TEXT," +
+            Document.COLUMN_DISPLAY_NAME + " TEXT NOT NULL," +
+            Document.COLUMN_SUMMARY + " TEXT," +
+            Document.COLUMN_LAST_MODIFIED + " INTEGER," +
+            Document.COLUMN_ICON + " INTEGER," +
+            Document.COLUMN_FLAGS + " INTEGER NOT NULL," +
+            Document.COLUMN_SIZE + " INTEGER NOT NULL);";
+
+    static final String QUERY_CREATE_ROOT_EXTRA =
+            "CREATE TABLE " + TABLE_ROOT_EXTRA + " (" +
+            Root.COLUMN_ROOT_ID + " INTEGER PRIMARY KEY," +
+            Root.COLUMN_FLAGS + " INTEGER NOT NULL," +
+            Root.COLUMN_AVAILABLE_BYTES + " INTEGER NOT NULL," +
+            Root.COLUMN_CAPACITY_BYTES + " INTEGER NOT NULL," +
+            Root.COLUMN_MIME_TYPES + " TEXT NOT NULL);";
+
+    /**
+     * Creates a view to join Documents table and RootExtra table on their primary keys to
+     * provide DocumentContract.Root equivalent information.
+     */
+    static final String QUERY_CREATE_VIEW_ROOTS =
+            "CREATE VIEW " + VIEW_ROOTS + " AS SELECT " +
+                    TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " +
+                            Root.COLUMN_ROOT_ID + "," +
+                    TABLE_ROOT_EXTRA + "." + Root.COLUMN_FLAGS + "," +
+                    TABLE_DOCUMENTS + "." + Document.COLUMN_ICON + " AS " +
+                            Root.COLUMN_ICON + "," +
+                    TABLE_DOCUMENTS + "." + Document.COLUMN_DISPLAY_NAME + " AS " +
+                            Root.COLUMN_TITLE + "," +
+                    TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY + " AS " +
+                            Root.COLUMN_SUMMARY + "," +
+                    TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " +
+                    Root.COLUMN_DOCUMENT_ID + "," +
+                    TABLE_ROOT_EXTRA + "." + Root.COLUMN_AVAILABLE_BYTES + "," +
+                    TABLE_ROOT_EXTRA + "." + Root.COLUMN_CAPACITY_BYTES + "," +
+                    TABLE_ROOT_EXTRA + "." + Root.COLUMN_MIME_TYPES + "," +
+                    TABLE_DOCUMENTS + "." + COLUMN_ROW_STATE +
+            " FROM " + TABLE_DOCUMENTS + " INNER JOIN " + TABLE_ROOT_EXTRA +
+            " ON " +
+                    COLUMN_PARENT_DOCUMENT_ID + " IS NULL AND " +
+                    TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID +
+                    "=" +
+                    TABLE_ROOT_EXTRA + "." + Root.COLUMN_ROOT_ID;
+}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
new file mode 100644
index 0000000..730012d
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
@@ -0,0 +1,391 @@
+/*
+ * 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.mtp;
+
+import static com.android.mtp.MtpDatabaseConstants.*;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+
+import java.util.Objects;
+
+/**
+ * Class that provides operations processing SQLite database directly.
+ */
+class MtpDatabaseInternal {
+    private static class OpenHelper extends SQLiteOpenHelper {
+        public OpenHelper(Context context) {
+            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL(QUERY_CREATE_DOCUMENTS);
+            db.execSQL(QUERY_CREATE_ROOT_EXTRA);
+            db.execSQL(QUERY_CREATE_VIEW_ROOTS);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private final SQLiteDatabase mDatabase;
+
+    MtpDatabaseInternal(Context context) {
+        final OpenHelper helper = new OpenHelper(context);
+        mDatabase = helper.getWritableDatabase();
+    }
+
+    Cursor queryRoots(String[] columnNames) {
+        return mDatabase.query(
+                VIEW_ROOTS,
+                columnNames,
+                COLUMN_ROW_STATE + " IN (?, ?)",
+                strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
+                null,
+                null,
+                null);
+    }
+
+    Cursor queryRootDocuments(String[] columnNames) {
+        return mDatabase.query(
+                TABLE_DOCUMENTS,
+                columnNames,
+                COLUMN_ROW_STATE + " IN (?, ?)",
+                strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
+                null,
+                null,
+                null);
+    }
+
+    Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
+        return mDatabase.query(
+                TABLE_DOCUMENTS,
+                columnNames,
+                COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_PARENT_DOCUMENT_ID + " = ?",
+                strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, parentDocumentId),
+                null,
+                null,
+                null);
+    }
+
+    /**
+     * Starts adding new documents.
+     * The methods decides mapping mode depends on if all documents under the given parent have MTP
+     * identifier or not. If all the documents have MTP identifier, it uses the identifier to find
+     * a corresponding existing row. Otherwise it does heuristic.
+     *
+     * @param selection Query matches valid documents.
+     * @param arg Argument for selection.
+     * @return Mapping mode.
+     */
+    int startAddingDocuments(String selection, String arg) {
+        mDatabase.beginTransaction();
+        try {
+            // Delete all pending rows.
+            deleteDocumentsAndRoots(
+                    selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING));
+
+            // Set all documents as invalidated.
+            final ContentValues values = new ContentValues();
+            values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
+            mDatabase.update(TABLE_DOCUMENTS, values, selection, new String[] { arg });
+
+            // If we have rows that does not have MTP identifier, do heuristic mapping by name.
+            final boolean useNameForResolving = DatabaseUtils.queryNumEntries(
+                    mDatabase,
+                    TABLE_DOCUMENTS,
+                    selection + " AND " + COLUMN_STORAGE_ID + " IS NULL",
+                    new String[] { arg }) > 0;
+            mDatabase.setTransactionSuccessful();
+            return useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER;
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    /**
+     * Puts the documents into the database.
+     * If the mapping mode is not heuristic, it just adds the rows to the database or updates the
+     * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as
+     * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then
+     * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
+     * rows.
+     *
+     * @param valuesList Values that are stored in the database.
+     * @param selection SQL where closure to select rows that shares the same parent.
+     * @param arg Argument for selection SQL.
+     * @param heuristic Whether the mapping mode is heuristic.
+     * @return List of Document ID inserted to the table.
+     */
+    long[] putDocuments(
+            ContentValues[] valuesList,
+            String selection,
+            String arg,
+            boolean heuristic,
+            String mappingKey) {
+        mDatabase.beginTransaction();
+        try {
+            final long[] documentIds = new long[valuesList.length];
+            int i = 0;
+            for (final ContentValues values : valuesList) {
+                final Cursor candidateCursor = mDatabase.query(
+                        TABLE_DOCUMENTS,
+                        strings(Document.COLUMN_DOCUMENT_ID),
+                        selection + " AND " +
+                        COLUMN_ROW_STATE + "=? AND " +
+                        mappingKey + "=?",
+                        strings(arg, ROW_STATE_INVALIDATED, values.getAsString(mappingKey)),
+                        null,
+                        null,
+                        null,
+                        "1");
+                final long rowId;
+                if (candidateCursor.getCount() == 0) {
+                    rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+                } else if (!heuristic) {
+                    candidateCursor.moveToNext();
+                    final String documentId = candidateCursor.getString(0);
+                    rowId = mDatabase.update(
+                            TABLE_DOCUMENTS, values, SELECTION_DOCUMENT_ID, strings(documentId));
+                } else {
+                    values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
+                    rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+                }
+                // Document ID is a primary integer key of the table. So the returned row
+                // IDs should be same with the document ID.
+                documentIds[i++] = rowId;
+                candidateCursor.close();
+            }
+
+            mDatabase.setTransactionSuccessful();
+            return documentIds;
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    void putRootExtra(ContentValues values) {
+        mDatabase.replace(TABLE_ROOT_EXTRA, null, values);
+    }
+
+    /**
+     * Maps 'pending' document and 'invalidated' document that shares the same column of groupKey.
+     * If the database does not find corresponding 'invalidated' document, it just removes
+     * 'invalidated' document from the database.
+     * @param selection Query to select rows for resolving.
+     * @param arg Argument for selection SQL.
+     * @param groupKey Column name used to find corresponding rows.
+     */
+    void stopAddingDocuments(String selection, String arg, String groupKey) {
+        mDatabase.beginTransaction();
+        try {
+            // Get 1-to-1 mapping of invalidated document and pending document.
+            final String invalidatedIdQuery = createStateFilter(
+                    ROW_STATE_INVALIDATED, Document.COLUMN_DOCUMENT_ID);
+            final String pendingIdQuery = createStateFilter(
+                    ROW_STATE_PENDING, Document.COLUMN_DOCUMENT_ID);
+            // SQL should be like:
+            // SELECT group_concat(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END),
+            //        group_concat(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END)
+            // WHERE device_id = ? AND parent_document_id IS NULL
+            // GROUP BY display_name
+            // HAVING count(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END) = 1 AND
+            //        count(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END) = 1
+            final Cursor mergingCursor = mDatabase.query(
+                    TABLE_DOCUMENTS,
+                    new String[] {
+                            "group_concat(" + invalidatedIdQuery + ")",
+                            "group_concat(" + pendingIdQuery + ")"
+                    },
+                    selection,
+                    strings(arg),
+                    groupKey,
+                    "count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1",
+                    null);
+
+            final ContentValues values = new ContentValues();
+            while (mergingCursor.moveToNext()) {
+                final String invalidatedId = mergingCursor.getString(0);
+                final String pendingId = mergingCursor.getString(1);
+
+                // Obtain the new values including the latest object handle from mapping row.
+                getFirstRow(
+                        TABLE_DOCUMENTS,
+                        SELECTION_DOCUMENT_ID,
+                        new String[] { pendingId },
+                        values);
+                values.remove(Document.COLUMN_DOCUMENT_ID);
+                values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
+                mDatabase.update(
+                        TABLE_DOCUMENTS,
+                        values,
+                        SELECTION_DOCUMENT_ID,
+                        new String[] { invalidatedId });
+
+                getFirstRow(
+                        TABLE_ROOT_EXTRA,
+                        SELECTION_ROOT_ID,
+                        new String[] { pendingId },
+                        values);
+                if (values.size() > 0) {
+                    values.remove(Root.COLUMN_ROOT_ID);
+                    mDatabase.update(
+                            TABLE_ROOT_EXTRA,
+                            values,
+                            SELECTION_ROOT_ID,
+                            new String[] { invalidatedId });
+                }
+
+                // Delete 'pending' row.
+                deleteDocumentsAndRoots(SELECTION_DOCUMENT_ID, new String[] { pendingId });
+            }
+            mergingCursor.close();
+
+            // Delete all invalidated rows that cannot be mapped.
+            deleteDocumentsAndRoots(
+                    COLUMN_ROW_STATE + " = ? AND " + selection,
+                    strings(ROW_STATE_INVALIDATED, arg));
+
+            // The database cannot find old document ID for the pending rows.
+            // Turn the all pending rows into valid state, which means the rows become to be
+            // valid with new document ID.
+            values.clear();
+            values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
+            mDatabase.update(
+                    TABLE_DOCUMENTS,
+                    values,
+                    COLUMN_ROW_STATE + " = ? AND " + selection,
+                    strings(ROW_STATE_PENDING, arg));
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    /**
+     * Clears MTP related identifier.
+     * It clears MTP's object handle and storage ID that are not stable over MTP sessions and mark
+     * the all documents as 'invalidated'. It also remove 'pending' rows as adding is cancelled
+     * now.
+     */
+    void clearMapping() {
+        mDatabase.beginTransaction();
+        try {
+            deleteDocumentsAndRoots(COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_PENDING));
+            final ContentValues values = new ContentValues();
+            values.putNull(COLUMN_OBJECT_HANDLE);
+            values.putNull(COLUMN_STORAGE_ID);
+            values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
+            mDatabase.update(TABLE_DOCUMENTS, values, null, null);
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    void beginTransaction() {
+        mDatabase.beginTransaction();
+    }
+
+    void setTransactionSuccessful() {
+        mDatabase.setTransactionSuccessful();
+    }
+
+    void endTransaction() {
+        mDatabase.endTransaction();
+    }
+
+    /**
+     * Deletes a document, and its root information if the document is a root document.
+     * @param selection Query to select documents.
+     * @param args Arguments for selection.
+     */
+    private void deleteDocumentsAndRoots(String selection, String[] args) {
+        mDatabase.beginTransaction();
+        try {
+            mDatabase.delete(
+                    TABLE_ROOT_EXTRA,
+                    Root.COLUMN_ROOT_ID + " IN (" + SQLiteQueryBuilder.buildQueryString(
+                            false,
+                            TABLE_DOCUMENTS,
+                            new String[] { Document.COLUMN_DOCUMENT_ID },
+                            selection,
+                            null,
+                            null,
+                            null,
+                            null) + ")",
+                    args);
+            mDatabase.delete(TABLE_DOCUMENTS, selection, args);
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    /**
+     * Obtains values of the first row for the query.
+     * @param values ContentValues that the values are stored to.
+     * @param table Target table.
+     * @param selection Query to select rows.
+     * @param args Argument for query.
+     */
+    private void getFirstRow(String table, String selection, String[] args, ContentValues values) {
+        values.clear();
+        final Cursor cursor = mDatabase.query(table, null, selection, args, null, null, null, "1");
+        if (cursor.getCount() == 0) {
+            return;
+        }
+        cursor.moveToNext();
+        DatabaseUtils.cursorRowToContentValues(cursor, values);
+        cursor.close();
+    }
+
+    /**
+     * Gets SQL expression that represents the given value or NULL depends on the row state.
+     * @param state Expected row state.
+     * @param a SQL value.
+     * @return Expression that represents a if the row state is expected one, and represents NULL
+     *     otherwise.
+     */
+    private static String createStateFilter(int state, String a) {
+        return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
+                " THEN " + a + " ELSE NULL END";
+    }
+
+    /**
+     * Converts values into string array.
+     * @param args Values converted into string array.
+     * @return String array.
+     */
+    private static String[] strings(Object... args) {
+        final String[] results = new String[args.length];
+        for (int i = 0; i < args.length; i++) {
+            results[i] = Objects.toString(args[i]);
+        }
+        return results;
+    }
+}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index 3878ba6..05345e1 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -28,9 +28,9 @@
 public class MtpDatabaseTest extends AndroidTestCase {
     private final String[] COLUMN_NAMES = new String[] {
         DocumentsContract.Document.COLUMN_DOCUMENT_ID,
-        MtpDatabase.COLUMN_DEVICE_ID,
-        MtpDatabase.COLUMN_STORAGE_ID,
-        MtpDatabase.COLUMN_OBJECT_HANDLE,
+        MtpDatabaseConstants.COLUMN_DEVICE_ID,
+        MtpDatabaseConstants.COLUMN_STORAGE_ID,
+        MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
         DocumentsContract.Document.COLUMN_MIME_TYPE,
         DocumentsContract.Document.COLUMN_DISPLAY_NAME,
         DocumentsContract.Document.COLUMN_SUMMARY,
@@ -42,13 +42,9 @@
 
     private final TestResources resources = new TestResources();
 
-    @Override
-    public void tearDown() {
-        MtpDatabase.deleteDatabase(getContext());
-    }
-
     public void testPutRootDocuments() throws Exception {
         final MtpDatabase database = new MtpDatabase(getContext());
+        database.startAddingRootDocuments(0);
         database.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
                 new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""),
@@ -141,7 +137,7 @@
 
     public void testPutChildDocuments() throws Exception {
         final MtpDatabase database = new MtpDatabase(getContext());
-
+        database.startAddingChildDocuments("parentId");
         database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
@@ -209,13 +205,14 @@
         final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
-                MtpDatabase.COLUMN_STORAGE_ID,
+                MtpDatabaseConstants.COLUMN_STORAGE_ID,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
         final String[] rootColumns = new String[] {
                 Root.COLUMN_ROOT_ID,
                 Root.COLUMN_AVAILABLE_BYTES
         };
+        database.startAddingRootDocuments(0);
         database.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""),
                 new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "")
@@ -275,6 +272,7 @@
             cursor.close();
         }
 
+        database.startAddingRootDocuments(0);
         database.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""),
                 new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "")
@@ -313,7 +311,7 @@
             cursor.close();
         }
 
-        database.resolveRootDocuments(0);
+        database.stopAddingRootDocuments(0);
 
         {
             final Cursor cursor = database.queryRootDocuments(columns);
@@ -346,9 +344,10 @@
         final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
-                MtpDatabase.COLUMN_OBJECT_HANDLE,
+                MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
+        database.startAddingChildDocuments("parentId");
         database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
@@ -378,6 +377,7 @@
             cursor.close();
         }
 
+        database.startAddingChildDocuments("parentId");
         database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
@@ -395,7 +395,7 @@
             cursor.close();
         }
 
-        database.resolveChildDocuments("parentId");
+        database.stopAddingChildDocuments("parentId");
 
         {
             final Cursor cursor = database.queryChildDocuments(columns, "parentId");
@@ -418,13 +418,15 @@
         final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
-                MtpDatabase.COLUMN_STORAGE_ID,
+                MtpDatabaseConstants.COLUMN_STORAGE_ID,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
         final String[] rootColumns = new String[] {
                 Root.COLUMN_ROOT_ID,
                 Root.COLUMN_AVAILABLE_BYTES
         };
+        database.startAddingRootDocuments(0);
+        database.startAddingRootDocuments(1);
         database.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, "")
         });
@@ -460,14 +462,16 @@
 
         database.clearMapping();
 
+        database.startAddingRootDocuments(0);
+        database.startAddingRootDocuments(1);
         database.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "")
         });
         database.putRootDocuments(1, resources, new MtpRoot[] {
                 new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "")
         });
-        database.resolveRootDocuments(0);
-        database.resolveRootDocuments(1);
+        database.stopAddingRootDocuments(0);
+        database.stopAddingRootDocuments(1);
 
         {
             final Cursor cursor = database.queryRootDocuments(columns);
@@ -500,8 +504,11 @@
         final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
-                MtpDatabase.COLUMN_OBJECT_HANDLE
+                MtpDatabaseConstants.COLUMN_OBJECT_HANDLE
         };
+
+        database.startAddingChildDocuments("parentId1");
+        database.startAddingChildDocuments("parentId2");
         database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
@@ -509,13 +516,16 @@
                 createDocument(101, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
         database.clearMapping();
+
+        database.startAddingChildDocuments("parentId1");
+        database.startAddingChildDocuments("parentId2");
         database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
         database.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
                 createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        database.resolveChildDocuments("parentId1");
+        database.stopAddingChildDocuments("parentId1");
 
         {
             final Cursor cursor = database.queryChildDocuments(columns, "parentId1");
@@ -539,25 +549,32 @@
         final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
-                MtpDatabase.COLUMN_STORAGE_ID,
+                MtpDatabaseConstants.COLUMN_STORAGE_ID,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
         final String[] rootColumns = new String[] {
                 Root.COLUMN_ROOT_ID,
                 Root.COLUMN_AVAILABLE_BYTES
         };
+
+        database.startAddingRootDocuments(0);
         database.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
         });
         database.clearMapping();
+
+        database.startAddingRootDocuments(0);
         database.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
         });
         database.clearMapping();
+
+        database.startAddingRootDocuments(0);
         database.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""),
         });
-        database.resolveRootDocuments(0);
+        database.stopAddingRootDocuments(0);
+
         {
             final Cursor cursor = database.queryRootDocuments(columns);
             assertEquals(1, cursor.getCount());
@@ -581,22 +598,27 @@
         final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
-                MtpDatabase.COLUMN_STORAGE_ID,
+                MtpDatabaseConstants.COLUMN_STORAGE_ID,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
         final String[] rootColumns = new String[] {
                 Root.COLUMN_ROOT_ID,
                 Root.COLUMN_AVAILABLE_BYTES
         };
+
+        database.startAddingRootDocuments(0);
         database.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
         });
         database.clearMapping();
+
+        database.startAddingRootDocuments(0);
         database.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
                 new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""),
         });
-        database.resolveRootDocuments(0);
+        database.stopAddingRootDocuments(0);
+
         {
             final Cursor cursor = database.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
@@ -622,4 +644,71 @@
             cursor.close();
         }
     }
+
+    public void testReplaceExistingRoots() {
+        // The client code should be able to replace exisitng rows with new information.
+        final MtpDatabase database = new MtpDatabase(getContext());
+        // Add one.
+        database.startAddingRootDocuments(0);
+        database.putRootDocuments(0, resources, new MtpRoot[] {
+                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+        });
+        database.stopAddingRootDocuments(0);
+        // Replace it.
+        database.startAddingRootDocuments(0);
+        database.putRootDocuments(0, resources, new MtpRoot[] {
+                new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
+        });
+        database.stopAddingRootDocuments(0);
+        {
+            final String[] columns = new String[] {
+                    DocumentsContract.Document.COLUMN_DOCUMENT_ID,
+                    MtpDatabaseConstants.COLUMN_STORAGE_ID,
+                    DocumentsContract.Document.COLUMN_DISPLAY_NAME
+            };
+            final Cursor cursor = database.queryRootDocuments(columns);
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("documentId", 1, cursor.getInt(0));
+            assertEquals("storageId", 100, cursor.getInt(1));
+            assertEquals("name", "Device Storage B", cursor.getString(2));
+            cursor.close();
+        }
+        {
+            final String[] columns = new String[] {
+                    Root.COLUMN_ROOT_ID,
+                    Root.COLUMN_AVAILABLE_BYTES
+            };
+            final Cursor cursor = database.queryRoots(columns);
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("rootId", 1, cursor.getInt(0));
+            assertEquals("availableBytes", 1000, cursor.getInt(1));
+            cursor.close();
+        }
+    }
+
+    public void _testFailToReplaceExisitingUnmappedRoots() {
+        // The client code should not be able to replace rows before resolving 'unmapped' rows.
+        final MtpDatabase database = new MtpDatabase(getContext());
+        // Add one.
+        database.startAddingRootDocuments(0);
+        database.putRootDocuments(0, resources, new MtpRoot[] {
+                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+        });
+        database.clearMapping();
+        // Add one.
+        database.putRootDocuments(0, resources, new MtpRoot[] {
+                new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
+        });
+        // Add one more before resolving unmapped documents.
+        try {
+            database.putRootDocuments(0, resources, new MtpRoot[] {
+                    new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
+            });
+            fail();
+        } catch (Throwable e) {
+            assertTrue(e instanceof Error);
+        }
+    }
 }
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 50237832..70abdf4 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -149,6 +149,9 @@
     <!-- Title for the prompt shown as a placeholder if no printers are found while not searching. [CHAR LIMIT=50] -->
     <string name="print_searching_for_printers">Searching for printers</string>
 
+    <!-- Title for the prompt shown as a placeholder if there are no print services. [CHAR LIMIT=50] -->
+    <string name="print_no_print_services">No print services enabled</string>
+
     <!-- Title for the prompt shown as a placeholder if there are no printers while searching. [CHAR LIMIT=50] -->
     <string name="print_no_printers">No printers found</string>
 
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 53e07e9..f409fd4 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -240,7 +240,9 @@
             throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_JOB
                     + " cannot be null");
         }
-        mPrintJob.setAttributes(new PrintAttributes.Builder().build());
+        if (mPrintJob.getAttributes() == null) {
+            mPrintJob.setAttributes(new PrintAttributes.Builder().build());
+        }
 
         final IBinder adapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER);
         if (adapter == null) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 3905bada..f4c15bd 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -84,6 +84,11 @@
 
     private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID";
 
+    /**
+     * If there are any enabled print services
+     */
+    private boolean mHasEnabledPrintServices;
+
     private final ArrayList<PrintServiceInfo> mAddPrinterServices =
             new ArrayList<>();
 
@@ -175,10 +180,6 @@
             }
         });
 
-        if (mAddPrinterServices.isEmpty()) {
-            menu.removeItem(R.id.action_add_printer);
-        }
-
         return true;
     }
 
@@ -230,6 +231,7 @@
     public void onResume() {
         super.onResume();
         updateServicesWithAddPrinterActivity();
+        updateEmptyView((DestinationAdapter)mListView.getAdapter());
         invalidateOptionsMenu();
     }
 
@@ -258,6 +260,7 @@
     }
 
     private void updateServicesWithAddPrinterActivity() {
+        mHasEnabledPrintServices = true;
         mAddPrinterServices.clear();
 
         // Get all enabled print services.
@@ -266,6 +269,7 @@
 
         // No enabled print services - done.
         if (enabledServices.isEmpty()) {
+            mHasEnabledPrintServices = false;
             return;
         }
 
@@ -324,7 +328,10 @@
         }
         TextView titleView = (TextView) findViewById(R.id.title);
         View progressBar = findViewById(R.id.progress_bar);
-        if (adapter.getUnfilteredCount() <= 0) {
+        if (!mHasEnabledPrintServices) {
+            titleView.setText(R.string.print_no_print_services);
+            progressBar.setVisibility(View.GONE);
+        } else if (adapter.getUnfilteredCount() <= 0) {
             titleView.setText(R.string.print_searching_for_printers);
             progressBar.setVisibility(View.VISIBLE);
         } else {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index e4b1ed8..d994841 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -808,7 +808,12 @@
             // The pairing dialog now warns of phone-book access for paired devices.
             // No separate prompt is displayed after pairing.
             if (getPhonebookPermissionChoice() == CachedBluetoothDevice.ACCESS_UNKNOWN) {
-                setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
+                if (mDevice.getBluetoothClass().getDeviceClass()
+                        == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
+                    setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
+                } else {
+                    setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_REJECTED);
+                }
             }
         }
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index f5fc698..6102bef 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -18,6 +18,7 @@
 import android.annotation.LayoutRes;
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.content.res.TypedArray;
 import android.os.Bundle;
 import android.support.v4.widget.DrawerLayout;
 import android.util.Pair;
@@ -54,6 +55,13 @@
             return;
         }
         Toolbar toolbar = (Toolbar) findViewById(R.id.action_bar);
+        TypedArray theme = getTheme().obtainStyledAttributes(android.R.styleable.Theme);
+        if (theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {
+            toolbar.setVisibility(View.GONE);
+            mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+            mDrawerLayout = null;
+            return;
+        }
         setActionBar(toolbar);
         mDrawerAdapter = new SettingsDrawerAdapter(this);
         ListView listView = (ListView) findViewById(R.id.left_drawer);
diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml
index 71aefad..ba991fb 100644
--- a/packages/SettingsProvider/AndroidManifest.xml
+++ b/packages/SettingsProvider/AndroidManifest.xml
@@ -8,11 +8,12 @@
                  android:process="system"
                  android:backupAgent="SettingsBackupAgent"
                  android:killAfterRestore="false"
-                 android:icon="@mipmap/ic_launcher_settings">
-                 
-    <!-- todo add: android:neverEncrypt="true" -->
+                 android:icon="@mipmap/ic_launcher_settings"
+                 android:forceDeviceEncrypted="true"
+                 android:encryptionAware="true">
 
-        <provider android:name="SettingsProvider" android:authorities="settings"
+        <provider android:name="SettingsProvider"
+                  android:authorities="settings"
                   android:multiprocess="false"
                   android:exported="true"
                   android:singleUser="true"
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 05591cc..8c39ee6 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -108,7 +108,9 @@
     <uses-permission android:name="android.permission.REGISTER_SIM_SUBSCRIPTION" />
     <uses-permission android:name="android.permission.GET_APP_OPS_STATS" />
 
-    <application android:label="@string/app_label">
+    <application android:label="@string/app_label"
+                 android:forceDeviceEncrypted="true"
+                 android:encryptionAware="true">
         <provider
             android:name="android.support.v4.content.FileProvider"
             android:authorities="com.android.shell"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index be5c0fe..6fda2c6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -145,7 +145,9 @@
         android:icon="@drawable/icon"
         android:process="com.android.systemui"
         android:supportsRtl="true"
-        android:theme="@style/systemui_theme">
+        android:theme="@style/systemui_theme"
+        android:forceDeviceEncrypted="true"
+        android:encryptionAware="true">
         <!-- Keep theme in sync with SystemUIApplication.onCreate().
              Setting the theme on the application does not affect views inflated by services.
              The application theme is set again from onCreate to take effect for those views. -->
diff --git a/packages/SystemUI/res/anim/recents_fast_toggle_app_home_exit.xml b/packages/SystemUI/res/anim/recents_fast_toggle_app_home_exit.xml
new file mode 100644
index 0000000..69edcc7
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_fast_toggle_app_home_exit.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+<!-- Recents Activity -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:zAdjustment="top">
+  <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
+         android:fillEnabled="true"
+         android:fillBefore="true" android:fillAfter="true"
+         android:duration="250"/>
+</set>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index bf5417d..95f1eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -27,6 +27,7 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.Display;
 import android.view.View;
@@ -193,6 +194,12 @@
      */
     @Override
     public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
+        // Ensure the device has been provisioned before allowing the user to interact with
+        // recents
+        if (!isDeviceProvisioned()) {
+            return;
+        }
+
         if (proxyToOverridePackage(ACTION_SHOW_RECENTS)) {
             return;
         }
@@ -222,6 +229,12 @@
      */
     @Override
     public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+        // Ensure the device has been provisioned before allowing the user to interact with
+        // recents
+        if (!isDeviceProvisioned()) {
+            return;
+        }
+
         if (proxyToOverridePackage(ACTION_HIDE_RECENTS)) {
             return;
         }
@@ -251,6 +264,12 @@
      */
     @Override
     public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
+        // Ensure the device has been provisioned before allowing the user to interact with
+        // recents
+        if (!isDeviceProvisioned()) {
+            return;
+        }
+
         if (proxyToOverridePackage(ACTION_TOGGLE_RECENTS)) {
             return;
         }
@@ -280,6 +299,12 @@
      */
     @Override
     public void preloadRecents() {
+        // Ensure the device has been provisioned before allowing the user to interact with
+        // recents
+        if (!isDeviceProvisioned()) {
+            return;
+        }
+
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
             mImpl.preloadRecents();
@@ -302,6 +327,12 @@
 
     @Override
     public void cancelPreloadingRecents() {
+        // Ensure the device has been provisioned before allowing the user to interact with
+        // recents
+        if (!isDeviceProvisioned()) {
+            return;
+        }
+
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
             mImpl.cancelPreloadingRecents();
@@ -329,11 +360,23 @@
 
     @Override
     public void showNextAffiliatedTask() {
+        // Ensure the device has been provisioned before allowing the user to interact with
+        // recents
+        if (!isDeviceProvisioned()) {
+            return;
+        }
+
         mImpl.showNextAffiliatedTask();
     }
 
     @Override
     public void showPrevAffiliatedTask() {
+        // Ensure the device has been provisioned before allowing the user to interact with
+        // recents
+        if (!isDeviceProvisioned()) {
+            return;
+        }
+
         mImpl.showPrevAffiliatedTask();
     }
 
@@ -456,6 +499,14 @@
     }
 
     /**
+     * @return whether this device is provisioned.
+     */
+    private boolean isDeviceProvisioned() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+    }
+
+    /**
      * Attempts to proxy the following action to the override recents package.
      * @return whether the proxying was successful
      */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 58f7124..3ae8827 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -42,7 +42,7 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.activity.IterateRecentsEvent;
@@ -115,29 +115,33 @@
     });
 
     /**
-     * A common Runnable to finish Recents either by calling finish() (with a custom animation) or
-     * launching Home with some ActivityOptions.  Generally we always launch home when we exit
-     * Recents rather than just finishing the activity since we don't know what is behind Recents in
-     * the task stack.  The only case where we finish() directly is when we are cancelling the full
-     * screen transition from the app.
+     * A common Runnable to finish Recents by launching Home with an animation depending on the
+     * last activity launch state.  Generally we always launch home when we exit Recents rather than
+     * just finishing the activity since we don't know what is behind Recents in the task stack.
      */
     class FinishRecentsRunnable implements Runnable {
         Intent mLaunchIntent;
-        ActivityOptions mLaunchOpts;
 
         /**
-         * Creates a finish runnable that starts the specified intent, using the given
-         * ActivityOptions.
+         * Creates a finish runnable that starts the specified intent.
          */
-        public FinishRecentsRunnable(Intent launchIntent, ActivityOptions opts) {
+        public FinishRecentsRunnable(Intent launchIntent) {
             mLaunchIntent = launchIntent;
-            mLaunchOpts = opts;
         }
 
         @Override
         public void run() {
             try {
-                startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(), UserHandle.CURRENT);
+                RecentsActivityLaunchState launchState =
+                        Recents.getConfiguration().getLaunchState();
+                ActivityOptions opts = ActivityOptions.makeCustomAnimation(RecentsActivity.this,
+                        launchState.launchedFromSearchHome ?
+                                R.anim.recents_to_search_launcher_enter :
+                                R.anim.recents_to_launcher_enter,
+                        launchState.launchedFromSearchHome ?
+                                R.anim.recents_to_search_launcher_exit :
+                                R.anim.recents_to_launcher_exit);
+                startActivityAsUser(mLaunchIntent, opts.toBundle(), UserHandle.CURRENT);
             } catch (Exception e) {
                 Log.e(TAG, getString(R.string.recents_launch_error_message, "Home"), e);
             }
@@ -191,18 +195,6 @@
             mRecentsView.setTaskStack(stack);
         }
 
-        // Create the home intent runnable
-        Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
-        homeIntent.addCategory(Intent.CATEGORY_HOME);
-        homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
-                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-        mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent,
-            ActivityOptions.makeCustomAnimation(this,
-                    launchState.launchedFromSearchHome ? R.anim.recents_to_search_launcher_enter :
-                        R.anim.recents_to_launcher_enter,
-                    launchState.launchedFromSearchHome ? R.anim.recents_to_search_launcher_exit :
-                        R.anim.recents_to_launcher_exit));
-
         // Mark the task that is the launch target
         int launchTaskIndexInStack = 0;
         if (launchState.launchedToTaskId != -1) {
@@ -361,6 +353,13 @@
         mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
         mScrimViews = new SystemBarScrimViews(this);
 
+        // Create the home intent runnable
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
+        homeIntent.addCategory(Intent.CATEGORY_HOME);
+        homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+        mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent);
+
         // Bind the search app widget when we first start up
         if (!Constants.DebugFlags.App.DisableSearchBar) {
             mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(this, mAppWidgetHost);
@@ -396,7 +395,7 @@
         boolean wasLaunchedByAm = !launchState.launchedFromHome &&
                 !launchState.launchedFromAppWithThumbnail;
         if (launchState.launchedHasConfigurationChanged || wasLaunchedByAm) {
-            EventBus.getDefault().send(new EnterRecentsWindowAnimationStartedEvent());
+            EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
         }
 
         if (!launchState.launchedHasConfigurationChanged) {
@@ -422,6 +421,12 @@
     }
 
     @Override
+    public void onEnterAnimationComplete() {
+        super.onEnterAnimationComplete();
+        EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
+    }
+
+    @Override
     protected void onPause() {
         super.onPause();
 
@@ -603,7 +608,7 @@
         }
     }
 
-    public final void onBusEvent(EnterRecentsWindowAnimationStartedEvent event) {
+    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
         // Try and start the enter animation (or restart it on configuration changed)
         ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
         ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 4059543..50aa2f7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -40,7 +40,6 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.activity.IterateRecentsEvent;
@@ -70,7 +69,7 @@
  * be called remotely from the system user.
  */
 public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
-        implements ActivityOptions.OnAnimationStartedListener, ActivityOptions.OnAnimationFinishedListener {
+        implements ActivityOptions.OnAnimationFinishedListener {
 
     private final static String TAG = "RecentsImpl";
     private final static boolean DEBUG = false;
@@ -140,7 +139,6 @@
     TaskStackListenerImpl mTaskStackListener;
     RecentsAppWidgetHost mAppWidgetHost;
     boolean mBootCompleted;
-    boolean mStartAnimationTriggered;
     boolean mCanReuseTaskStackViews = true;
 
     // Task launching
@@ -403,17 +401,22 @@
         ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
         // Return early if there is no running task
         if (runningTask == null) return;
-        // Return early if the running task is in the home stack (optimization)
-        if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
 
         // Find the task in the recents list
+        boolean isTopTaskHome = SystemServicesProxy.isHomeStack(runningTask.stackId);
         ArrayList<Task> tasks = focusedStack.getTasks();
         Task toTask = null;
         ActivityOptions launchOpts = null;
         int taskCount = tasks.size();
         for (int i = taskCount - 1; i >= 1; i--) {
             Task task = tasks.get(i);
-            if (task.key.id == runningTask.id) {
+            if (isTopTaskHome) {
+                toTask = tasks.get(i - 1);
+                launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+                        R.anim.recents_launch_next_affiliated_task_target,
+                        R.anim.recents_fast_toggle_app_home_exit);
+                break;
+            } else if (task.key.id == runningTask.id) {
                 toTask = tasks.get(i - 1);
                 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
                         R.anim.recents_launch_prev_affiliated_task_target,
@@ -613,7 +616,7 @@
         final Task toTask = new Task();
         final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
                 topTask.id, toTask);
-        ForegroundThread.getHandler().post(new Runnable() {
+        ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() {
             @Override
             public void run() {
                 final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
@@ -635,7 +638,7 @@
         return ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.recents_from_unknown_enter,
                 R.anim.recents_from_unknown_exit,
-                mHandler, this);
+                mHandler, null);
     }
 
     /**
@@ -646,12 +649,12 @@
             return ActivityOptions.makeCustomAnimation(mContext,
                     R.anim.recents_from_search_launcher_enter,
                     R.anim.recents_from_search_launcher_exit,
-                    mHandler, this);
+                    mHandler, null);
         }
         return ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.recents_from_launcher_enter,
                 R.anim.recents_from_launcher_exit,
-                mHandler, this);
+                mHandler, null);
     }
 
     /**
@@ -677,7 +680,7 @@
             AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
             specs.toArray(specsArray);
             return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
-                    specsArray, mHandler, this, this);
+                    specsArray, mHandler, null, this);
         } else {
             // Update the destination rect
             Task toTask = new Task();
@@ -688,7 +691,7 @@
             if (thumbnail != null) {
                 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
                         thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
-                        (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this);
+                        (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
             }
             // If both the screenshot and thumbnail fails, then just fall back to the default transition
             return getUnknownTransitionActivityOptions();
@@ -841,8 +844,6 @@
     private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
               ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
               TaskStackLayoutAlgorithm.VisibilityReport vr) {
-        mStartAnimationTriggered = false;
-
         // Update the configuration based on the launch options
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
@@ -870,16 +871,7 @@
         mCanReuseTaskStackViews = true;
     }
 
-    /**** OnAnimationStartedListener Implementation ****/
-
-    @Override
-    public void onAnimationStarted() {
-        // Notify recents to start the enter animation
-        if (!mStartAnimationTriggered) {
-            mStartAnimationTriggered = true;
-            EventBus.getDefault().post(new EnterRecentsWindowAnimationStartedEvent());
-        }
-    }
+    /**** OnAnimationFinishedListener Implementation ****/
 
     @Override
     public void onAnimationFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationCompletedEvent.java
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
rename to packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationCompletedEvent.java
index f187178..b31f320 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationCompletedEvent.java
@@ -19,8 +19,10 @@
 import com.android.systemui.recents.events.EventBus;
 
 /**
- * This is sent when the window animation into Recents starts.
+ * This is sent when the window animation into Recents completes.  We use this signal to know when
+ * we can start in-app animations so that they don't conflict with the window transition into
+ * Recents.
  */
-public class EnterRecentsWindowAnimationStartedEvent extends EventBus.Event {
+public class EnterRecentsWindowAnimationCompletedEvent extends EventBus.Event {
     // Simple event
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index a28601b..85b8fcf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -270,7 +270,7 @@
         int taskCount = tasks.size();
         for (int i = taskCount - 1; i >= 0; i--) {
             Task t = tasks.get(i);
-            if (t.isFreeformTask()) {
+            if (t.isFreeformTask() || targetStackId == FREEFORM_WORKSPACE_STACK_ID) {
                 TaskView tv = stackView.getChildViewForTask(t);
                 if (tv == null) {
                     // TODO: Create a different animation task rect for this case (though it should
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
index c4e2d8a..5a09ee4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -26,7 +26,7 @@
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
 
 /** Manages the scrims for the various system bars. */
 public class SystemBarScrimViews {
@@ -81,21 +81,11 @@
     /**
      * Starts animating the scrim views when entering Recents.
      */
-    public final void onBusEvent(EnterRecentsWindowAnimationStartedEvent event) {
-        RecentsConfiguration config = Recents.getConfiguration();
-        RecentsActivityLaunchState launchState = config.getLaunchState();
-        int transitionEnterFromAppDelay = mContext.getResources().getInteger(
-                R.integer.recents_enter_from_app_transition_duration);
-        int transitionEnterFromHomeDelay = mContext.getResources().getInteger(
-                R.integer.recents_enter_from_home_transition_duration);
-
+    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
         if (mHasStatusBarScrim && mShouldAnimateStatusBarScrim) {
             mStatusBarScrimView.setTranslationY(-mStatusBarScrimView.getMeasuredHeight());
             mStatusBarScrimView.animate()
                     .translationY(0)
-                    .setStartDelay(launchState.launchedFromHome ?
-                            transitionEnterFromHomeDelay :
-                            transitionEnterFromAppDelay)
                     .setDuration(mNavBarScrimEnterDuration)
                     .setInterpolator(mQuintOutInterpolator)
                     .withStartAction(new Runnable() {
@@ -110,9 +100,6 @@
             mNavBarScrimView.setTranslationY(mNavBarScrimView.getMeasuredHeight());
             mNavBarScrimView.animate()
                     .translationY(0)
-                    .setStartDelay(launchState.launchedFromHome ?
-                            transitionEnterFromHomeDelay :
-                            transitionEnterFromAppDelay)
                     .setDuration(mNavBarScrimEnterDuration)
                     .setInterpolator(mQuintOutInterpolator)
                     .withStartAction(new Runnable() {
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 9c8829f..a57ac9d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -1161,7 +1161,7 @@
             TaskView frontTv = getChildViewForTask(newFrontMostTask);
             if (frontTv != null) {
                 frontTv.onTaskBound(newFrontMostTask);
-                frontTv.fadeInActionButton(0, getResources().getInteger(
+                frontTv.fadeInActionButton(getResources().getInteger(
                         R.integer.recents_task_enter_from_app_duration));
             }
         }
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 4f4b91a..cb7465d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -313,10 +313,6 @@
         RecentsActivityLaunchState launchState = config.getLaunchState();
         Resources res = mContext.getResources();
         final TaskViewTransform transform = ctx.currentTaskTransform;
-        final int transitionEnterFromAppDelay = res.getInteger(
-                R.integer.recents_enter_from_app_transition_duration);
-        final int transitionEnterFromHomeDelay = res.getInteger(
-                R.integer.recents_enter_from_home_transition_duration);
         final int taskViewEnterFromAppDuration = res.getInteger(
                 R.integer.recents_task_enter_from_app_duration);
         final int taskViewEnterFromHomeDuration = res.getInteger(
@@ -332,25 +328,22 @@
                 if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) {
                     // Animate the thumbnail alpha before the dim animation (to prevent updating the
                     // hardware layer)
-                    mThumbnailView.startEnterRecentsAnimation(transitionEnterFromAppDelay,
-                            new Runnable() {
-                                @Override
-                                public void run() {
-                                    animateDimToProgress(0, taskViewEnterFromAppDuration,
-                                            ctx.postAnimationTrigger.decrementOnAnimationEnd());
-                                }
-                            });
+                    mThumbnailView.startEnterRecentsAnimation(new Runnable() {
+                            @Override
+                            public void run() {
+                                animateDimToProgress(taskViewEnterFromAppDuration,
+                                        ctx.postAnimationTrigger.decrementOnAnimationEnd());
+                            }
+                        });
                 } else {
                     // Immediately start the dim animation
-                    animateDimToProgress(transitionEnterFromAppDelay,
-                            taskViewEnterFromAppDuration,
+                    animateDimToProgress(taskViewEnterFromAppDuration,
                             ctx.postAnimationTrigger.decrementOnAnimationEnd());
                 }
                 ctx.postAnimationTrigger.increment();
 
                 // Animate the action button in
-                fadeInActionButton(transitionEnterFromAppDelay,
-                        taskViewEnterFromAppDuration);
+                fadeInActionButton(taskViewEnterFromAppDuration);
             } else {
                 // Animate the task up if it was occluding the launch target
                 if (ctx.currentTaskOccludesLaunchTarget) {
@@ -358,7 +351,6 @@
                     setAlpha(0f);
                     animate().alpha(1f)
                             .translationY(transform.translationY)
-                            .setStartDelay(transitionEnterFromAppDelay)
                             .setUpdateListener(null)
                             .setInterpolator(mFastOutSlowInInterpolator)
                             .setDuration(taskViewEnterFromHomeDuration)
@@ -377,8 +369,7 @@
         } else if (launchState.launchedFromHome) {
             // Animate the tasks up
             int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
-            int delay = transitionEnterFromHomeDelay +
-                    frontIndex * taskViewEnterFromHomeStaggerDelay;
+            int delay = frontIndex * taskViewEnterFromHomeStaggerDelay;
 
             setScaleX(transform.scale);
             setScaleY(transform.scale);
@@ -404,13 +395,12 @@
         }
     }
 
-    public void fadeInActionButton(int delay, int duration) {
+    public void fadeInActionButton(int duration) {
         // Hide the action button
         mActionButtonView.setAlpha(0f);
 
         // Animate the action button in
         mActionButtonView.animate().alpha(1f)
-                .setStartDelay(delay)
                 .setDuration(duration)
                 .setInterpolator(PhoneStatusBar.ALPHA_IN)
                 .start();
@@ -611,12 +601,11 @@
     }
 
     /** Animates the dim to the task progress. */
-    void animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable) {
+    void animateDimToProgress(int duration, Animator.AnimatorListener postAnimRunnable) {
         // Animate the dim into view as well
         int toDim = getDimFromTaskProgress();
         if (toDim != getDim()) {
             ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim);
-            anim.setStartDelay(delay);
             anim.setDuration(duration);
             if (postAnimRunnable != null) {
                 anim.addListener(postAnimRunnable);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index 690c297..bc50846 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -218,13 +218,13 @@
     void onFocusChanged(boolean focused) {
         if (focused) {
             if (Float.compare(getAlpha(), 1f) != 0) {
-                startFadeAnimation(1f, 0, 150, null);
+                startFadeAnimation(1f, 150, null);
             }
         } else {
             float taskViewThumbnailAlpha = getResources().getFloat(
                     R.dimen.recents_task_view_thumbnail_alpha);
             if (Float.compare(getAlpha(), taskViewThumbnailAlpha) != 0) {
-                startFadeAnimation(taskViewThumbnailAlpha, 0, 150, null);
+                startFadeAnimation(taskViewThumbnailAlpha, 150, null);
             }
         }
     }
@@ -244,10 +244,10 @@
     }
 
     /** Animates this task thumbnail as it enters Recents. */
-    void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) {
+    void startEnterRecentsAnimation(Runnable postAnimRunnable) {
         float taskViewThumbnailAlpha = getResources().getFloat(
                 R.dimen.recents_task_view_thumbnail_alpha);
-        startFadeAnimation(taskViewThumbnailAlpha, delay,
+        startFadeAnimation(taskViewThumbnailAlpha,
                 getResources().getInteger(R.integer.recents_task_enter_from_app_duration),
                 postAnimRunnable);
     }
@@ -256,14 +256,13 @@
     void startLaunchTaskAnimation(Runnable postAnimRunnable) {
         int taskViewExitToAppDuration = mContext.getResources().getInteger(
                 R.integer.recents_task_exit_to_app_duration);
-        startFadeAnimation(1f, 0, taskViewExitToAppDuration, postAnimRunnable);
+        startFadeAnimation(1f, taskViewExitToAppDuration, postAnimRunnable);
     }
 
     /** Starts a new thumbnail alpha animation. */
-    void startFadeAnimation(float finalAlpha, int delay, int duration, final Runnable postAnimRunnable) {
+    void startFadeAnimation(float finalAlpha, int duration, final Runnable postAnimRunnable) {
         Utilities.cancelAnimationWithoutCallbacks(mThumbnailAlphaAnimator);
         mThumbnailAlphaAnimator = ValueAnimator.ofFloat(mThumbnailAlpha, finalAlpha);
-        mThumbnailAlphaAnimator.setStartDelay(delay);
         mThumbnailAlphaAnimator.setDuration(duration);
         mThumbnailAlphaAnimator.setInterpolator(mFastOutSlowInInterpolator);
         mThumbnailAlphaAnimator.addUpdateListener(mThumbnailAlphaUpdateListener);
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 59d4011..a520a33 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -25,8 +25,10 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.Region.Op;
+import android.hardware.display.DisplayManager;
 import android.util.AttributeSet;
-import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.DisplayInfo;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -262,9 +264,13 @@
     }
 
     private void updateDisplayInfo() {
-        DisplayMetrics info = mContext.getResources().getDisplayMetrics();
-        mDisplayWidth = info.widthPixels;
-        mDisplayHeight = info.heightPixels;
+        final DisplayManager displayManager =
+                (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
+        Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+        final DisplayInfo info = new DisplayInfo();
+        display.getDisplayInfo(info);
+        mDisplayWidth = info.logicalWidth;
+        mDisplayHeight = info.logicalHeight;
     }
 
     private int calculatePosition(int touchX, int touchY) {
diff --git a/packages/services/Proxy/AndroidManifest.xml b/packages/services/Proxy/AndroidManifest.xml
index bbcd6b9..88f8381 100644
--- a/packages/services/Proxy/AndroidManifest.xml
+++ b/packages/services/Proxy/AndroidManifest.xml
@@ -7,7 +7,9 @@
 
     <application
         android:label="@string/app_label"
-        android:process="com.android.proxyhandler">
+        android:process="com.android.proxyhandler"
+        android:forceDeviceEncrypted="true"
+        android:encryptionAware="true">
 
         <service android:name=".ProxyService"
             android:exported="true">
diff --git a/rs/java/android/renderscript/ScriptGroup.java b/rs/java/android/renderscript/ScriptGroup.java
index 54180f4..9bbacbc 100644
--- a/rs/java/android/renderscript/ScriptGroup.java
+++ b/rs/java/android/renderscript/ScriptGroup.java
@@ -278,6 +278,8 @@
             public ValueAndSize(RenderScript rs, Object obj) {
                 if (obj instanceof Allocation) {
                     value = ((Allocation)obj).getID(rs);
+                    // Special value for size to tell the runtime and driver that
+                    // the value is an Allocation
                     size = -1;
                 } else if (obj instanceof Boolean) {
                     value = ((Boolean)obj).booleanValue() ? 1 : 0;
@@ -289,10 +291,10 @@
                     value = ((Long)obj).longValue();
                     size = 8;
                 } else if (obj instanceof Float) {
-                    value = ((Float)obj).longValue();
+                    value = Float.floatToRawIntBits(((Float)obj).floatValue());
                     size = 4;
                 } else if (obj instanceof Double) {
-                    value = ((Double)obj).longValue();
+                    value = Double.doubleToRawLongBits(((Double)obj).doubleValue());
                     size = 8;
                 }
             }
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index be7071e..113241d 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -393,7 +393,6 @@
 
   size_t numValues, numDependencies;
   RsScriptFieldID* fieldIDs;
-  uintptr_t* values;
   RsClosure* depClosures;
   RsScriptFieldID* depFieldIDs;
 
@@ -430,15 +429,6 @@
     fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
   }
 
-  values = (uintptr_t*)alloca(sizeof(uintptr_t) * numValues);
-  if (values == nullptr) {
-      goto exit;
-  }
-
-  for (size_t i = 0; i < numValues; i++) {
-    values[i] = (uintptr_t)jValues[i];
-  }
-
   depClosures = (RsClosure*)alloca(sizeof(RsClosure) * numDependencies);
   if (depClosures == nullptr) {
       goto exit;
@@ -459,7 +449,7 @@
 
   ret = (jlong)(uintptr_t)rsClosureCreate(
       (RsContext)con, (RsScriptKernelID)kernelID, (RsAllocation)returnValue,
-      fieldIDs, numValues, values, numValues,
+      fieldIDs, numValues, jValues, numValues,
       (int*)jSizes, numValues,
       depClosures, numDependencies,
       depFieldIDs, numDependencies);
@@ -511,7 +501,6 @@
 
   size_t numValues;
   RsScriptFieldID* fieldIDs;
-  uintptr_t* values;
 
   if (fieldIDs_length != values_length || values_length != sizes_length) {
       ALOGE("Unmatched field IDs, values, and sizes in closure creation.");
@@ -534,18 +523,9 @@
     fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
   }
 
-  values = (uintptr_t*)alloca(sizeof(uintptr_t) * numValues);
-  if (values == nullptr) {
-      goto exit;
-  }
-
-  for (size_t i = 0; i < numValues; i++) {
-    values[i] = (uintptr_t)jValues[i];
-  }
-
   ret = (jlong)(uintptr_t)rsInvokeClosureCreate(
       (RsContext)con, (RsScriptInvokeID)invokeID, jParams, jParamLength,
-      fieldIDs, numValues, values, numValues,
+      fieldIDs, numValues, jValues, numValues,
       (int*)jSizes, numValues);
 
 exit:
@@ -561,15 +541,17 @@
 static void
 nClosureSetArg(JNIEnv *_env, jobject _this, jlong con, jlong closureID,
                jint index, jlong value, jint size) {
+  // Size is signed with -1 indicating the value is an Allocation
   rsClosureSetArg((RsContext)con, (RsClosure)closureID, (uint32_t)index,
-                  (uintptr_t)value, (size_t)size);
+                  (uintptr_t)value, size);
 }
 
 static void
 nClosureSetGlobal(JNIEnv *_env, jobject _this, jlong con, jlong closureID,
                   jlong fieldID, jlong value, jint size) {
+  // Size is signed with -1 indicating the value is an Allocation
   rsClosureSetGlobal((RsContext)con, (RsClosure)closureID,
-                     (RsScriptFieldID)fieldID, (uintptr_t)value, (size_t)size);
+                     (RsScriptFieldID)fieldID, (int64_t)value, size);
 }
 
 static long
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index b4411cf..781d134 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -341,7 +341,7 @@
         mTransformationAnimator.start();
     }
 
-    private void setMagnificationSpec(MagnificationSpec spec) {
+    public void setMagnificationSpec(MagnificationSpec spec) {
         if (DEBUG_SET_MAGNIFICATION_SPEC) {
             Slog.i(LOG_TAG, "Sending: " + spec);
         }
@@ -351,6 +351,10 @@
         mWindowManager.setMagnificationSpec(MagnificationSpec.obtain(spec));
     }
 
+    public MagnificationSpec getMagnificationSpec() {
+        return mSentMagnificationSpec;
+    }
+
     private static class MagnificationSpecEvaluator implements TypeEvaluator<MagnificationSpec> {
         private final MagnificationSpec mTempTransformationSpec = MagnificationSpec.obtain();
 
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 02b5b8a..25fef18 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -79,10 +79,11 @@
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.widget.IRemoteViewsAdapterConnection;
 import com.android.internal.widget.IRemoteViewsFactory;
-
 import com.android.server.LocalServices;
 import com.android.server.WidgetBackupProvider;
+
 import libcore.io.IoUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -1954,8 +1955,13 @@
                 if (period < MIN_UPDATE_PERIOD) {
                     period = MIN_UPDATE_PERIOD;
                 }
-                mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                        SystemClock.elapsedRealtime() + period, period, provider.broadcast);
+                final long oldId = Binder.clearCallingIdentity();
+                try {
+                    mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                            SystemClock.elapsedRealtime() + period, period, provider.broadcast);
+                } finally {
+                    Binder.restoreCallingIdentity(oldId);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index fd67b41..f329cff 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -95,8 +95,6 @@
     private static final int MESSAGE_BLUETOOTH_STATE_CHANGE=60;
     private static final int MESSAGE_TIMEOUT_BIND =100;
     private static final int MESSAGE_TIMEOUT_UNBIND =101;
-    private static final int MESSAGE_GET_NAME_AND_ADDRESS=200;
-    private static final int MESSAGE_SAVE_NAME_AND_ADDRESS=201;
     private static final int MESSAGE_USER_SWITCHED = 300;
     private static final int MESSAGE_ADD_PROXY_DELAYED = 400;
     private static final int MESSAGE_BIND_PROFILE_SERVICE = 401;
@@ -587,15 +585,6 @@
         }
     }
 
-    /** @hide*/
-    public void getNameAndAddress() {
-        if (DBG) {
-            Log.d(TAG,"getNameAndAddress(): mBluetooth = " + mBluetooth +
-                  " mBinding = " + mBinding);
-        }
-        Message msg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
-        mHandler.sendMessage(msg);
-    }
     public boolean enableNoAutoConnect()
     {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
@@ -681,14 +670,13 @@
             if (mUnbinding) return;
             mUnbinding = true;
             if (mBluetooth != null) {
-                if (!mConnection.isGetNameAddressOnly()) {
-                    //Unregister callback object
-                    try {
-                        mBluetooth.unregisterCallback(mBluetoothCallback);
-                    } catch (RemoteException re) {
-                        Log.e(TAG, "Unable to unregister BluetoothCallback",re);
-                    }
+                //Unregister callback object
+                try {
+                    mBluetooth.unregisterCallback(mBluetoothCallback);
+                } catch (RemoteException re) {
+                    Log.e(TAG, "Unable to unregister BluetoothCallback",re);
                 }
+
                 if (DBG) Log.d(TAG, "Sending unbind request.");
                 mBluetooth = null;
                 //Unbind
@@ -780,11 +768,6 @@
             if (DBG) Log.d(TAG, "Auto-enabling Bluetooth.");
             sendEnableMsg(mQuietEnableExternal);
         }
-        if (!isNameAndAddressSet()) {
-            // Sync the Bluetooth name and address from the Bluetooth Adapter
-            if (DBG) Log.d(TAG, "Retrieving Bluetooth Adapter name and address...");
-            getNameAndAddress();
-        }
     }
 
     /**
@@ -957,42 +940,38 @@
      * Inform BluetoothAdapter instances that Adapter service is up
      */
     private void sendBluetoothServiceUpCallback() {
-        if (!mConnection.isGetNameAddressOnly()) {
-            if (DBG) Log.d(TAG,"Calling onBluetoothServiceUp callbacks");
-            try {
-                int n = mCallbacks.beginBroadcast();
-                Log.d(TAG,"Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
-                for (int i=0; i <n;i++) {
-                    try {
-                        mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
-                    }  catch (RemoteException e) {
-                        Log.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
-                    }
+        if (DBG) Log.d(TAG,"Calling onBluetoothServiceUp callbacks");
+        try {
+            int n = mCallbacks.beginBroadcast();
+            Log.d(TAG,"Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
+            for (int i=0; i <n;i++) {
+                try {
+                    mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
+                }  catch (RemoteException e) {
+                    Log.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
                 }
-            } finally {
-                mCallbacks.finishBroadcast();
             }
+        } finally {
+            mCallbacks.finishBroadcast();
         }
     }
     /**
      * Inform BluetoothAdapter instances that Adapter service is down
      */
     private void sendBluetoothServiceDownCallback() {
-        if (!mConnection.isGetNameAddressOnly()) {
-            if (DBG) Log.d(TAG,"Calling onBluetoothServiceDown callbacks");
-            try {
-                int n = mCallbacks.beginBroadcast();
-                Log.d(TAG,"Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
-                for (int i=0; i <n;i++) {
-                    try {
-                        mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
-                    }  catch (RemoteException e) {
-                        Log.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
-                    }
+        if (DBG) Log.d(TAG,"Calling onBluetoothServiceDown callbacks");
+        try {
+            int n = mCallbacks.beginBroadcast();
+            Log.d(TAG,"Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
+            for (int i=0; i <n;i++) {
+                try {
+                    mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
+                }  catch (RemoteException e) {
+                    Log.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
                 }
-            } finally {
-                mCallbacks.finishBroadcast();
             }
+        } finally {
+            mCallbacks.finishBroadcast();
         }
     }
 
@@ -1052,17 +1031,6 @@
     }
 
     private class BluetoothServiceConnection implements ServiceConnection {
-
-        private boolean mGetNameAddressOnly;
-
-        public void setGetNameAddressOnly(boolean getOnly) {
-            mGetNameAddressOnly = getOnly;
-        }
-
-        public boolean isGetNameAddressOnly() {
-            return mGetNameAddressOnly;
-        }
-
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "BluetoothServiceConnection: " + className.getClassName());
             Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED);
@@ -1108,104 +1076,6 @@
         public void handleMessage(Message msg) {
             if (DBG) Log.d (TAG, "Message: " + msg.what);
             switch (msg.what) {
-                case MESSAGE_GET_NAME_AND_ADDRESS: {
-                    if (DBG) Log.d(TAG,"MESSAGE_GET_NAME_AND_ADDRESS");
-                    synchronized(mConnection) {
-                        //Start bind request
-                        if ((mBluetooth == null) && (!mBinding)) {
-                            if (DBG) Log.d(TAG, "Binding to service to get name and address");
-                            mConnection.setGetNameAddressOnly(true);
-                            //Start bind timeout and bind
-                            Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
-                            mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);
-                            Intent i = new Intent(IBluetooth.class.getName());
-                            if (!doBind(i, mConnection,
-                                    Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
-                                    UserHandle.CURRENT)) {
-                                mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
-                            } else {
-                                mBinding = true;
-                            }
-                        }
-                        else {
-                            Message saveMsg= mHandler.obtainMessage(MESSAGE_SAVE_NAME_AND_ADDRESS);
-                            saveMsg.arg1 = 0;
-                            if (mBluetooth != null) {
-                                mHandler.sendMessage(saveMsg);
-                            } else {
-                                // if enable is also called to bind the service
-                                // wait for MESSAGE_BLUETOOTH_SERVICE_CONNECTED
-                                mHandler.sendMessageDelayed(saveMsg, TIMEOUT_SAVE_MS);
-                            }
-                        }
-                    }
-                    break;
-                }
-                case MESSAGE_SAVE_NAME_AND_ADDRESS: {
-                    boolean unbind = false;
-                    if (DBG) Log.d(TAG,"MESSAGE_SAVE_NAME_AND_ADDRESS");
-                    synchronized(mConnection) {
-                        if (!mEnable && mBluetooth != null && !mConnection.isGetNameAddressOnly()) {
-                            try {
-                                mBluetooth.enable();
-                            } catch (RemoteException e) {
-                                Log.e(TAG,"Unable to call enable()",e);
-                            }
-                        }
-                    }
-                    if (mBluetooth != null && !mConnection.isGetNameAddressOnly()) waitForOnOff(true, false);
-                    synchronized(mConnection) {
-                        if (mBluetooth != null) {
-                            String name =  null;
-                            String address = null;
-                            try {
-                                name =  mBluetooth.getName();
-                                address = mBluetooth.getAddress();
-                            } catch (RemoteException re) {
-                                Log.e(TAG,"",re);
-                            }
-
-                            if (name != null && address != null) {
-                                storeNameAndAddress(name,address);
-                                if (mConnection.isGetNameAddressOnly()) {
-                                    unbind = true;
-                                }
-                            } else {
-                                if (msg.arg1 < MAX_SAVE_RETRIES) {
-                                    Message retryMsg = mHandler.obtainMessage(MESSAGE_SAVE_NAME_AND_ADDRESS);
-                                    retryMsg.arg1= 1+msg.arg1;
-                                    if (DBG) Log.d(TAG,"Retrying name/address remote retrieval and save.....Retry count =" + retryMsg.arg1);
-                                    mHandler.sendMessageDelayed(retryMsg, TIMEOUT_SAVE_MS);
-                                } else {
-                                    Log.w(TAG,"Maximum name/address remote retrieval retry exceeded");
-                                    if (mConnection.isGetNameAddressOnly()) {
-                                        unbind = true;
-                                    }
-                                }
-                            }
-                            if (!mEnable && !mConnection.isGetNameAddressOnly()) {
-                                try {
-                                    mBluetooth.disable();
-                                } catch (RemoteException e) {
-                                    Log.e(TAG,"Unable to call disable()",e);
-                                }
-                            }
-                        } else {
-                            // rebind service by Request GET NAME AND ADDRESS
-                            // if service is unbinded by disable or
-                            // MESSAGE_BLUETOOTH_SERVICE_CONNECTED is not received
-                            Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
-                            mHandler.sendMessage(getMsg);
-                        }
-                    }
-                    if (!mEnable && mBluetooth != null && !mConnection.isGetNameAddressOnly()) {
-                        waitForOnOff(false, true);
-                    }
-                    if (unbind) {
-                        unbindAndFinish();
-                    }
-                    break;
-                }
                 case MESSAGE_ENABLE:
                     if (DBG) {
                         Log.d(TAG, "MESSAGE_ENABLE: mBluetooth = " + mBluetooth);
@@ -1308,14 +1178,6 @@
                             Log.e(TAG,"Unable to call configHciSnoopLog", e);
                         }
 
-                        if (mConnection.isGetNameAddressOnly()) {
-                            //Request GET NAME AND ADDRESS
-                            Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
-                            mHandler.sendMessage(getMsg);
-                            if (!mEnable) return;
-                        }
-
-                        mConnection.setGetNameAddressOnly(false);
                         //Register callback object
                         try {
                             mBluetooth.registerCallback(mBluetoothCallback);
@@ -1412,25 +1274,23 @@
                             SERVICE_RESTART_TIME_MS);
                     }
 
-                    if (!mConnection.isGetNameAddressOnly()) {
-                        sendBluetoothServiceDownCallback();
+                    sendBluetoothServiceDownCallback();
 
-                        // Send BT state broadcast to update
-                        // the BT icon correctly
-                        if ((mState == BluetoothAdapter.STATE_TURNING_ON) ||
-                            (mState == BluetoothAdapter.STATE_ON)) {
-                            bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON,
-                                                        BluetoothAdapter.STATE_TURNING_OFF);
-                            mState = BluetoothAdapter.STATE_TURNING_OFF;
-                        }
-                        if (mState == BluetoothAdapter.STATE_TURNING_OFF) {
-                            bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
-                                                        BluetoothAdapter.STATE_OFF);
-                        }
-
-                        mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
-                        mState = BluetoothAdapter.STATE_OFF;
+                    // Send BT state broadcast to update
+                    // the BT icon correctly
+                    if ((mState == BluetoothAdapter.STATE_TURNING_ON) ||
+                        (mState == BluetoothAdapter.STATE_ON)) {
+                        bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON,
+                                                    BluetoothAdapter.STATE_TURNING_OFF);
+                        mState = BluetoothAdapter.STATE_TURNING_OFF;
                     }
+                    if (mState == BluetoothAdapter.STATE_TURNING_OFF) {
+                        bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
+                                                    BluetoothAdapter.STATE_OFF);
+                    }
+
+                    mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+                    mState = BluetoothAdapter.STATE_OFF;
                     break;
                 }
                 case MESSAGE_RESTART_BLUETOOTH_SERVICE:
@@ -1539,7 +1399,6 @@
                 //Start bind timeout and bind
                 Message timeoutMsg=mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
                 mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);
-                mConnection.setGetNameAddressOnly(false);
                 Intent i = new Intent(IBluetooth.class.getName());
                 if (!doBind(i, mConnection,Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
                         UserHandle.CURRENT)) {
@@ -1548,21 +1407,6 @@
                     mBinding = true;
                 }
             } else if (mBluetooth != null) {
-                if (mConnection.isGetNameAddressOnly()) {
-                    // if GetNameAddressOnly is set, we can clear this flag,
-                    // so the service won't be unbind
-                    // after name and address are saved
-                    mConnection.setGetNameAddressOnly(false);
-                    //Register callback object
-                    try {
-                        mBluetooth.registerCallback(mBluetoothCallback);
-                    } catch (RemoteException re) {
-                        Log.e(TAG, "Unable to register BluetoothCallback",re);
-                    }
-                    //Inform BluetoothAdapter instances that service is up
-                    sendBluetoothServiceUpCallback();
-                }
-
                 //Enable bluetooth
                 try {
                     if (!mQuietEnable) {
@@ -1594,9 +1438,7 @@
 
     private void handleDisable() {
         synchronized(mConnection) {
-            // don't need to disable if GetNameAddressOnly is set,
-            // service will be unbinded after Name and Address are saved
-            if ((mBluetooth != null) && (!mConnection.isGetNameAddressOnly())) {
+            if (mBluetooth != null) {
                 if (DBG) Log.d(TAG,"Sending off request.");
 
                 try {
diff --git a/services/core/java/com/android/server/BluetoothService.java b/services/core/java/com/android/server/BluetoothService.java
index 73e8c52..019d03d 100644
--- a/services/core/java/com/android/server/BluetoothService.java
+++ b/services/core/java/com/android/server/BluetoothService.java
@@ -31,14 +31,15 @@
 
     @Override
     public void onStart() {
-        Log.d(TAG, "onStart: publishing BluetoothManagerService");
-        publishBinderService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, mBluetoothManagerService);
     }
 
     @Override
     public void onBootPhase(int phase) {
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
             Log.d(TAG, "onBootPhase: PHASE_SYSTEM_SERVICES_READY");
+            publishBinderService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, mBluetoothManagerService);
+        } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+            Log.d(TAG, "onBootPhase: PHASE_ACTIVITY_MANAGER_READY");
             mBluetoothManagerService.handleOnBootPhase();
         }
     }
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 6e4f238..37dd884 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -96,6 +96,7 @@
 import com.android.internal.os.Zygote;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.HexDump;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.server.NativeDaemonConnector.Command;
@@ -119,6 +120,7 @@
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.KeySpec;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -284,6 +286,8 @@
 
     @GuardedBy("mLock")
     private int[] mStartedUsers = EmptyArray.INT;
+    @GuardedBy("mLock")
+    private int[] mUnlockedUsers = EmptyArray.INT;
 
     /** Map from disk ID to disk */
     @GuardedBy("mLock")
@@ -402,6 +406,17 @@
         }
     }
 
+    private static String escapeNull(String arg) {
+        if (TextUtils.isEmpty(arg)) {
+            return "!";
+        } else {
+            if (arg.indexOf('\0') != -1 || arg.indexOf(' ') != -1) {
+                throw new IllegalArgumentException(arg);
+            }
+            return arg;
+        }
+    }
+
     /** List of crypto types.
       * These must match CRYPT_TYPE_XXX in cryptfs.h AND their
       * corresponding commands in CommandListener.cpp */
@@ -1892,6 +1907,9 @@
             if ((mask & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0) {
                 mForceAdoptable = (flags & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0;
             }
+            if ((mask & StorageManager.DEBUG_EMULATE_FBE) != 0) {
+                // TODO: persist through vold and reboot
+            }
 
             writeSettingsLocked();
             mHandler.obtainMessage(H_RESET).sendToTarget();
@@ -2654,65 +2672,99 @@
     }
 
     @Override
-    public void createNewUserDir(int userHandle, String path) {
-        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-            throw new SecurityException("Only SYSTEM_UID can create user directories");
-        }
-
+    public void createUserKey(int userId, int serialNumber) {
+        enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
         waitForReady();
 
-        if (DEBUG_EVENTS) {
-            Slog.i(TAG, "Creating new user dir");
-        }
-
         try {
-            NativeDaemonEvent event = mCryptConnector.execute(
-                "cryptfs", "createnewuserdir", userHandle, path);
-            if (!"0".equals(event.getMessage())) {
-                String error = "createnewuserdir sent unexpected message: "
-                    + event.getMessage();
-                Slog.e(TAG,  error);
-                // ext4enc:TODO is this the right exception?
-                throw new RuntimeException(error);
-            }
+            mCryptConnector.execute("cryptfs", "create_user_key", userId, serialNumber);
         } catch (NativeDaemonConnectorException e) {
-            Slog.e(TAG, "createnewuserdir threw exception", e);
-            throw new RuntimeException("createnewuserdir threw exception", e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
-    // ext4enc:TODO duplication between this and createNewUserDir is nasty
     @Override
-    public void deleteUserKey(int userHandle) {
-        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-            throw new SecurityException("Only SYSTEM_UID can delete user keys");
-        }
-
+    public void destroyUserKey(int userId) {
+        enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
         waitForReady();
 
-        if (DEBUG_EVENTS) {
-            Slog.i(TAG, "Deleting user key");
+        try {
+            mCryptConnector.execute("cryptfs", "destroy_user_key", userId);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void unlockUserKey(int userId, int serialNumber, byte[] token) {
+        enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+        waitForReady();
+
+        final String encodedToken;
+        if (ArrayUtils.isEmpty(token)) {
+            encodedToken = "!";
+        } else {
+            encodedToken = HexDump.toHexString(token);
         }
 
         try {
-            NativeDaemonEvent event = mCryptConnector.execute(
-                "cryptfs", "deleteuserkey", userHandle);
-            if (!"0".equals(event.getMessage())) {
-                String error = "deleteuserkey sent unexpected message: "
-                    + event.getMessage();
-                Slog.e(TAG,  error);
-                // ext4enc:TODO is this the right exception?
-                throw new RuntimeException(error);
-            }
+            mCryptConnector.execute("cryptfs", "unlock_user_key", userId, serialNumber,
+                    new SensitiveArg(encodedToken));
         } catch (NativeDaemonConnectorException e) {
-            Slog.e(TAG, "deleteuserkey threw exception", e);
-            throw new RuntimeException("deleteuserkey threw exception", e);
+            throw e.rethrowAsParcelableException();
+        }
+
+        synchronized (mLock) {
+            mUnlockedUsers = ArrayUtils.appendInt(mUnlockedUsers, userId);
+        }
+    }
+
+    @Override
+    public void lockUserKey(int userId) {
+        enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+        waitForReady();
+
+        try {
+            mCryptConnector.execute("cryptfs", "lock_user_key", userId);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+
+        synchronized (mLock) {
+            mUnlockedUsers = ArrayUtils.removeInt(mUnlockedUsers, userId);
+        }
+    }
+
+    @Override
+    public boolean isUserKeyUnlocked(int userId) {
+        if (SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false)) {
+            synchronized (mLock) {
+                return ArrayUtils.contains(mUnlockedUsers, userId);
+            }
+        } else {
+            return true;
+        }
+    }
+
+    @Override
+    public void prepareUserStorage(String volumeUuid, int userId, int serialNumber) {
+        enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+        waitForReady();
+
+        try {
+            mCryptConnector.execute("cryptfs", "prepare_user_storage", escapeNull(volumeUuid),
+                    userId, serialNumber);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
         }
     }
 
     @Override
     public boolean isPerUserEncryptionEnabled() {
-        return "file".equals(SystemProperties.get("ro.crypto.type", "none"));
+        // TODO: switch this over to a single property; currently using two to
+        // handle the emulated case
+        return "file".equals(SystemProperties.get("ro.crypto.type", "none"))
+                || SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false);
     }
 
     @Override
@@ -3449,6 +3501,9 @@
             pw.println();
             pw.println("Primary storage UUID: " + mPrimaryStorageUuid);
             pw.println("Force adoptable: " + mForceAdoptable);
+            pw.println();
+            pw.println("Started users: " + Arrays.toString(mStartedUsers));
+            pw.println("Unlocked users: " + Arrays.toString(mUnlockedUsers));
         }
 
         synchronized (mObbMounts) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2b776b9..566065c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -55,8 +55,6 @@
 import android.app.IAppTask;
 import android.app.ITaskStackListener;
 import android.app.ProfilerInfo;
-import android.app.admin.DevicePolicyManagerInternal;
-import android.app.admin.IDevicePolicyManager;
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
 import android.app.usage.UsageEvents;
@@ -334,6 +332,11 @@
     // before we decide it must be hung.
     static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000;
 
+    // How long we will retain processes hosting content providers in the "last activity"
+    // state before allowing them to drop down to the regular cached LRU list.  This is
+    // to avoid thrashing of provider processes under low memory situations.
+    static final int CONTENT_PROVIDER_RETAIN_TIME = 20*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
     // started with a wrapper for instrumentation (such as Valgrind) because it
@@ -504,6 +507,11 @@
      */
     SparseArray<String[]> mLockTaskPackages = new SparseArray<>();
 
+    /**
+     * The package name of the DeviceOwner. This package is not permitted to have its data cleared.
+     */
+    String mDeviceOwnerName;
+
     final UserController mUserController;
 
     public class PendingAssistExtras extends Binder implements Runnable {
@@ -5134,12 +5142,8 @@
     public boolean clearApplicationUserData(final String packageName,
             final IPackageDataObserver observer, int userId) {
         enforceNotIsolatedCaller("clearApplicationUserData");
-
-        final DevicePolicyManagerInternal dpmi =
-                LocalServices.getService(DevicePolicyManagerInternal.class);
-        if (dpmi != null && dpmi.isDeviceAdminPackage(userId, packageName)) {
-            throw new SecurityException(
-                    "Clearing DeviceAdmin/DeviceOwner/ProfileOwner data is forbidden.");
+        if (packageName != null && packageName.equals(mDeviceOwnerName)) {
+            throw new SecurityException("Clearing DeviceOwner data is forbidden.");
         }
         int uid = Binder.getCallingUid();
         int pid = Binder.getCallingPid();
@@ -9240,6 +9244,17 @@
     }
 
     @Override
+    public void updateDeviceOwner(String packageName) {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
+            throw new SecurityException("updateDeviceOwner called from non-system process");
+        }
+        synchronized (this) {
+            mDeviceOwnerName = packageName;
+        }
+    }
+
+    @Override
     public void updateLockTaskPackages(int userId, String[] packages) {
         final int callingUid = Binder.getCallingUid();
         if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
@@ -9636,6 +9651,14 @@
             if (conn.stableCount == 0 && conn.unstableCount == 0) {
                 cpr.connections.remove(conn);
                 conn.client.conProviders.remove(conn);
+                if (conn.client.setProcState < ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
+                    // The client is more important than last activity -- note the time this
+                    // is happening, so we keep the old provider process around a bit as last
+                    // activity to avoid thrashing it.
+                    if (cpr.proc != null) {
+                        cpr.proc.lastProviderTime = SystemClock.uptimeMillis();
+                    }
+                }
                 stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid, cpr.name);
                 return true;
             }
@@ -12079,8 +12102,6 @@
                 }
             }
 
-            enableSystemUserApps();
-
             // Start up initial activity.
             mBooting = true;
             startHomeActivityLocked(currentUserId, "systemReady");
@@ -12132,43 +12153,6 @@
         }
     }
 
-    private void enableSystemUserApps() {
-        // For system user, enable apps based on the following conditions:
-        // - app is whitelisted; or has no launcher icons; or has INTERACT_ACROSS_USERS permission
-        // - app is not in the blacklist
-        if (UserManager.isSplitSystemUser()) {
-            AppsQueryHelper queryHelper = new AppsQueryHelper(mContext);
-            Set<String> enableApps = new HashSet<>();
-            enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_NON_LAUNCHABLE_APPS
-                            | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM
-                            | AppsQueryHelper.GET_DEFAULT_IMES,
-                            /* systemAppsOnly */ true, UserHandle.SYSTEM));
-            ArraySet<String> wlApps = SystemConfig.getInstance().getSystemUserWhitelistedApps();
-            enableApps.addAll(wlApps);
-            ArraySet<String> blApps = SystemConfig.getInstance().getSystemUserBlacklistedApps();
-            enableApps.removeAll(blApps);
-
-            List<String> systemApps = queryHelper.queryApps(0, /* systemAppsOnly */ true,
-                    UserHandle.SYSTEM);
-            final int systemAppsSize = systemApps.size();
-            for (int i = 0; i < systemAppsSize; i++) {
-                String pName = systemApps.get(i);
-                boolean enable = enableApps.contains(pName);
-                try {
-                    if (enable) {
-                        AppGlobals.getPackageManager().installExistingPackageAsUser(pName,
-                                UserHandle.USER_SYSTEM);
-                    } else {
-                        AppGlobals.getPackageManager().deletePackageAsUser(pName, null,
-                                UserHandle.USER_SYSTEM, PackageManager.DELETE_SYSTEM_APP);
-                    }
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Error occured when processing package " + pName, e);
-                }
-            }
-        }
-    }
-
     private boolean makeAppCrashingLocked(ProcessRecord app,
             String shortMsg, String longMsg, String stackTrace) {
         app.crashing = true;
@@ -18390,6 +18374,18 @@
             }
         }
 
+        if (app.lastProviderTime > 0 && (app.lastProviderTime+CONTENT_PROVIDER_RETAIN_TIME) > now) {
+            if (adj > ProcessList.PREVIOUS_APP_ADJ) {
+                adj = ProcessList.PREVIOUS_APP_ADJ;
+                schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
+                app.cached = false;
+                app.adjType = "provider";
+            }
+            if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
+                procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
+            }
+        }
+
         if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) {
             // A client of one of our services or providers is in the top state.  We
             // *may* want to be in the top state, but not if we are already running in
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index ba6e9b1c..9896ec5 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1338,7 +1338,12 @@
             return topHomeActivity == null || !topHomeActivity.isHomeActivity();
         }
 
-        final int belowFocusedIndex = mStacks.indexOf(focusedStack) - 1;
+        // Find the first stack below focused stack that actually got something visible.
+        int belowFocusedIndex = mStacks.indexOf(focusedStack) - 1;
+        while (belowFocusedIndex >= 0 &&
+                mStacks.get(belowFocusedIndex).topRunningActivityLocked() == null) {
+            belowFocusedIndex--;
+        }
         if ((focusedStackId == DOCKED_STACK_ID || focusedStackId == PINNED_STACK_ID)
                 && stackIndex == belowFocusedIndex) {
             // Stacks directly behind the docked or pinned stack are always visible.
@@ -3103,6 +3108,9 @@
             // If the activity is PAUSING, we will complete the finish once
             // it is done pausing; else we can just directly finish it here.
             if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish not pausing: " + r);
+            if (r.visible) {
+                mWindowManager.setAppVisibility(r.appToken, false);
+            }
             return finishCurrentActivityLocked(r, FINISH_AFTER_PAUSE, oomAdj) == null;
         } else {
             if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish waiting for pause of: " + r);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 4fc8454..13d3ee1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3340,6 +3340,12 @@
             return;
         }
 
+        if (task.stack != null && task.stack.mStackId == stackId) {
+            // You are already in the right stack silly...
+            Slog.i(TAG, "moveTaskToStack: taskId=" + taskId + " already in stackId=" + stackId);
+            return;
+        }
+
         final ActivityRecord topActivity = task.getTopActivity();
         if (StackId.preserveWindowOnTaskMove(stackId) && topActivity != null) {
             // We are about to relaunch the activity because its configuration changed due to
@@ -3356,15 +3362,13 @@
 
         // Make sure the task has the appropriate bounds/size for the stack it is in.
         if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
-            resizeTaskLocked(task, stack.mBounds,
-                    RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
+            resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
         } else if (stackId == FREEFORM_WORKSPACE_STACK_ID
                 && task.mBounds == null && task.mLastNonFullscreenBounds != null) {
             resizeTaskLocked(task, task.mLastNonFullscreenBounds,
                     RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
         } else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
-            resizeTaskLocked(task, stack.mBounds,
-                    RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
+            resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
         }
 
         // The task might have already been running and its visibility needs to be synchronized with
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b77eec8..4bfe300 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -136,6 +136,7 @@
     long curCpuTime;            // How long proc has run CPU most recently
     long lastRequestedGc;       // When we last asked the app to do a gc
     long lastLowMemory;         // When we last told the app that memory is low
+    long lastProviderTime;      // The last time someone else was using a provider in this process.
     boolean reportLowMemory;    // Set to true when waiting to report low mem
     boolean empty;              // Is this an empty background process?
     boolean cached;             // Is this a cached process?
@@ -317,6 +318,11 @@
                     pw.print(" foregroundActivities="); pw.print(foregroundActivities);
                     pw.print(" (rep="); pw.print(repForegroundActivities); pw.println(")");
         }
+        if (lastProviderTime > 0) {
+            pw.print(prefix); pw.print("lastProviderTime=");
+            TimeUtils.formatDuration(lastProviderTime, now, pw);
+            pw.println();
+        }
         if (hasStartedServices) {
             pw.print(prefix); pw.print("hasStartedServices="); pw.println(hasStartedServices);
         }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 096c85e..b214080 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -572,8 +572,8 @@
      * Removes all associated thumbnail data when a task is removed or pruned from recents.
      */
     void disposeThumbnail() {
+        mLastThumbnailInfo.reset();
         mLastThumbnail = null;
-        mLastThumbnailInfo = null;
         lastDescription = null;
     }
 
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index cbc13fe..d6fced6 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -18,7 +18,6 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-
 import static android.app.ActivityManager.USER_OP_IS_CURRENT;
 import static android.app.ActivityManager.USER_OP_SUCCESS;
 import static android.os.Process.SYSTEM_UID;
@@ -57,8 +56,11 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.storage.IMountService;
+import android.os.storage.StorageManager;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -134,7 +136,9 @@
         mService = service;
         mHandler = mService.mHandler;
         // User 0 is the first and only user that runs at boot.
-        mStartedUsers.put(UserHandle.USER_SYSTEM, new UserState(UserHandle.SYSTEM, true));
+        final UserState uss = new UserState(UserHandle.SYSTEM);
+        mStartedUsers.put(UserHandle.USER_SYSTEM, uss);
+        updateUserUnlockedState(uss);
         mUserLru.add(UserHandle.USER_SYSTEM);
         updateStartedUserArrayLocked();
     }
@@ -409,6 +413,21 @@
         return userManager;
     }
 
+    private void updateUserUnlockedState(UserState uss) {
+        final IMountService mountService = IMountService.Stub
+                .asInterface(ServiceManager.getService(Context.STORAGE_SERVICE));
+        if (mountService != null) {
+            try {
+                uss.unlocked = mountService.isUserKeyUnlocked(uss.mHandle.getIdentifier());
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            // System isn't fully booted yet, so guess based on property
+            uss.unlocked = !SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false);
+        }
+    }
+
     boolean startUser(final int userId, final boolean foreground) {
         if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -453,11 +472,14 @@
                 // If the user we are switching to is not currently started, then
                 // we need to start it now.
                 if (mStartedUsers.get(userId) == null) {
-                    mStartedUsers.put(userId, new UserState(new UserHandle(userId), false));
+                    mStartedUsers.put(userId, new UserState(new UserHandle(userId)));
                     updateStartedUserArrayLocked();
                     needStart = true;
                 }
 
+                final UserState uss = mStartedUsers.get(userId);
+                updateUserUnlockedState(uss);
+
                 final Integer userIdInt = userId;
                 mUserLru.remove(userIdInt);
                 mUserLru.add(userIdInt);
@@ -479,8 +501,6 @@
                     mUserLru.add(currentUserIdInt);
                 }
 
-                final UserState uss = mStartedUsers.get(userId);
-
                 // Make sure user is in the started state.  If it is currently
                 // stopping, we need to knock that off.
                 if (uss.mState == UserState.STATE_STOPPING) {
@@ -956,8 +976,11 @@
             return true;
         }
         if ((flags & ActivityManager.FLAG_WITH_AMNESIA) != 0) {
-            // TODO: add in amnesia lifecycle
-            return false;
+            // If user is currently locked, we fall through to default "running"
+            // behavior below
+            if (state.unlocked) {
+                return false;
+            }
         }
         return state.mState != UserState.STATE_STOPPING
                 && state.mState != UserState.STATE_SHUTDOWN;
diff --git a/services/core/java/com/android/server/am/UserState.java b/services/core/java/com/android/server/am/UserState.java
index b3d82bc..b5b5c1d 100644
--- a/services/core/java/com/android/server/am/UserState.java
+++ b/services/core/java/com/android/server/am/UserState.java
@@ -40,6 +40,7 @@
     public int mState = STATE_BOOTING;
     public boolean switching;
     public boolean initializing;
+    public boolean unlocked;
 
     /**
      * The last time that a provider was reported to usage stats as being brought to important
@@ -47,7 +48,7 @@
      */
     public final ArrayMap<String,Long> mProviderLastReportedFg = new ArrayMap<>();
 
-    public UserState(UserHandle handle, boolean initial) {
+    public UserState(UserHandle handle) {
         mHandle = handle;
     }
 
@@ -62,6 +63,11 @@
         }
         if (switching) pw.print(" SWITCHING");
         if (initializing) pw.print(" INITIALIZING");
+        if (unlocked) {
+            pw.print(" UNLOCKED");
+        } else {
+            pw.print(" LOCKED");
+        }
         pw.println();
     }
 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 058d681..fe9fe50 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1068,6 +1068,7 @@
         if (DEBUG_VOL) {
             Log.d(TAG, String.format("Master mute %s, user=%d", masterMute, currentUser));
         }
+        setSystemAudioMute(masterMute);
         AudioSystem.setMasterMute(masterMute);
         broadcastMasterMuteStatus(masterMute);
 
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index c1aaf07..6687412 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1353,18 +1353,9 @@
                     if (iface != null) {
                         String[] dnsServers = mDefaultDnsServers;
                         Collection<InetAddress> dnses = linkProperties.getDnsServers();
-                        if (dnses != null) {
-                            // we currently only handle IPv4
-                            ArrayList<InetAddress> v4Dnses =
-                                    new ArrayList<InetAddress>(dnses.size());
-                            for (InetAddress dnsAddress : dnses) {
-                                if (dnsAddress instanceof Inet4Address) {
-                                    v4Dnses.add(dnsAddress);
-                                }
-                            }
-                            if (v4Dnses.size() > 0) {
-                                dnsServers = NetworkUtils.makeStrings(v4Dnses);
-                            }
+                        if (dnses != null && !dnses.isEmpty()) {
+                            // TODO: remove this invocation of NetworkUtils.makeStrings().
+                            dnsServers = NetworkUtils.makeStrings(dnses);
                         }
                         try {
                             Network network = getConnectivityManager().getNetworkForType(upType);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 150c849..99a051a 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -185,15 +185,6 @@
         return mInstaller.execute(builder.toString());
     }
 
-    public int rename(String oldname, String newname) {
-        StringBuilder builder = new StringBuilder("rename");
-        builder.append(' ');
-        builder.append(oldname);
-        builder.append(' ');
-        builder.append(newname);
-        return mInstaller.execute(builder.toString());
-    }
-
     @Deprecated
     public int fixUid(String name, int uid, int gid) {
         return fixUid(null, name, uid, gid);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ad1cbe6..c152514 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -105,6 +105,7 @@
 import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.AppsQueryHelper;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IOnPermissionsChangeListener;
 import android.content.pm.IPackageDataObserver;
@@ -1810,10 +1811,48 @@
             boolean factoryTest, boolean onlyCore) {
         PackageManagerService m = new PackageManagerService(context, installer,
                 factoryTest, onlyCore);
+        m.enableSystemUserApps();
         ServiceManager.addService("package", m);
         return m;
     }
 
+    private void enableSystemUserApps() {
+        if (!UserManager.isSplitSystemUser()) {
+            return;
+        }
+        // For system user, enable apps based on the following conditions:
+        // - app is whitelisted or belong to one of these groups:
+        //   -- system app which has no launcher icons
+        //   -- system app which has INTERACT_ACROSS_USERS permission
+        //   -- system IME app
+        // - app is not in the blacklist
+        AppsQueryHelper queryHelper = new AppsQueryHelper(this);
+        Set<String> enableApps = new ArraySet<>();
+        enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_NON_LAUNCHABLE_APPS
+                | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM
+                | AppsQueryHelper.GET_IMES, /* systemAppsOnly */ true, UserHandle.SYSTEM));
+        ArraySet<String> wlApps = SystemConfig.getInstance().getSystemUserWhitelistedApps();
+        enableApps.addAll(wlApps);
+        ArraySet<String> blApps = SystemConfig.getInstance().getSystemUserBlacklistedApps();
+        enableApps.removeAll(blApps);
+
+        List<String> systemApps = queryHelper.queryApps(0, /* systemAppsOnly */ true,
+                UserHandle.SYSTEM);
+        final int systemAppsSize = systemApps.size();
+        synchronized (mPackages) {
+            for (int i = 0; i < systemAppsSize; i++) {
+                String pName = systemApps.get(i);
+                PackageSetting pkgSetting = mSettings.mPackages.get(pName);
+                // Should not happen, but we shouldn't be failing if it does
+                if (pkgSetting == null) {
+                    continue;
+                }
+                boolean installed = enableApps.contains(pName);
+                pkgSetting.setInstalled(installed, UserHandle.USER_SYSTEM);
+            }
+        }
+    }
+
     static String[] splitString(String str, char sep) {
         int count = 1;
         int i = 0;
@@ -2981,23 +3020,25 @@
      * purposefully done before acquiring {@link #mPackages} lock.
      */
     private int augmentFlagsForUser(int flags, int userId) {
-        // TODO: bring back once locking fixed
-//        final IActivityManager am = ActivityManagerNative.getDefault();
-//        if (am == null) {
-//            // We must be early in boot, so the best we can do is assume the
-//            // user is fully running.
-//            return flags;
-//        }
-//        final long token = Binder.clearCallingIdentity();
-//        try {
-//            if (am.isUserRunning(userId, ActivityManager.FLAG_WITH_AMNESIA)) {
-//                flags |= PackageManager.FLAG_USER_RUNNING_WITH_AMNESIA;
-//            }
-//        } catch (RemoteException e) {
-//            throw e.rethrowAsRuntimeException();
-//        } finally {
-//            Binder.restoreCallingIdentity(token);
-//        }
+        if (SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false)) {
+            final IMountService mount = IMountService.Stub
+                    .asInterface(ServiceManager.getService(Context.STORAGE_SERVICE));
+            if (mount == null) {
+                // We must be early in boot, so the best we can do is assume the
+                // user is fully running.
+                return flags;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (!mount.isUserKeyUnlocked(userId)) {
+                    flags |= PackageManager.FLAG_USER_RUNNING_WITH_AMNESIA;
+                }
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
         return flags;
     }
 
@@ -12768,8 +12809,14 @@
                 ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
         try {
             if (dpm != null) {
+                final ComponentName deviceOwnerComponentName = dpm.getDeviceOwner();
+                final String deviceOwnerPackageName = deviceOwnerComponentName == null ? null
+                        : deviceOwnerComponentName.getPackageName();
                 // Does the package contains the device owner?
-                if (dpm.isDeviceOwnerPackage(packageName)) {
+                // TODO Do we have to do it even if userId != UserHandle.USER_ALL?  Otherwise,
+                // this check is probably not needed, since DO should be registered as a device
+                // admin on some user too. (Original bug for this: b/17657954)
+                if (packageName.equals(deviceOwnerPackageName)) {
                     return true;
                 }
                 // Does it contain a device admin for any user?
@@ -15918,13 +15965,14 @@
             }
         }
 
+        final StorageManager sm = mContext.getSystemService(StorageManager.class);
         final UserManager um = mContext.getSystemService(UserManager.class);
         for (UserInfo user : um.getUsers()) {
             final File userDir = Environment.getDataUserDirectory(volumeUuid, user.id);
             if (userDir.exists()) continue;
 
             try {
-                UserManagerService.prepareUserDirectory(mContext, volumeUuid, user.id);
+                sm.prepareUserStorage(volumeUuid, user.id, user.serialNumber);
                 UserManagerService.enforceSerialNumber(userDir, user.serialNumber);
             } catch (IOException e) {
                 Log.wtf(TAG, "Failed to create user directory on " + volumeUuid, e);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 9a87abe..3a1d2de 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1563,13 +1563,15 @@
                             userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
                         }
                     }
+
                     final StorageManager storage = mContext.getSystemService(StorageManager.class);
+                    storage.createUserKey(userId, userInfo.serialNumber);
                     for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
                         final String volumeUuid = vol.getFsUuid();
                         try {
                             final File userDir = Environment.getDataUserDirectory(volumeUuid,
                                     userId);
-                            prepareUserDirectory(mContext, volumeUuid, userId);
+                            storage.prepareUserStorage(volumeUuid, userId, userInfo.serialNumber);
                             enforceSerialNumber(userDir, userInfo.serialNumber);
                         } catch (IOException e) {
                             Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e);
@@ -1797,8 +1799,7 @@
     }
 
     private void removeUserStateLILP(final int userHandle) {
-        mContext.getSystemService(StorageManager.class)
-            .deleteUserKey(userHandle);
+        mContext.getSystemService(StorageManager.class).destroyUserKey(userHandle);
         // Cleanup package manager settings
         mPm.cleanUpUserLILPw(this, userHandle);
 
@@ -2183,16 +2184,6 @@
     }
 
     /**
-     * Create new {@code /data/user/[id]} directory and sets default
-     * permissions.
-     */
-    public static void prepareUserDirectory(Context context, String volumeUuid, int userId) {
-        final StorageManager storage = context.getSystemService(StorageManager.class);
-        final File userDir = Environment.getDataUserDirectory(volumeUuid, userId);
-        storage.createNewUserDir(userId, userDir);
-    }
-
-    /**
      * Enforce that serial number stored in user directory inode matches the
      * given expected value. Gracefully sets the serial number if currently
      * undefined.
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index ac79b36..97713fc 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -54,6 +54,15 @@
                 @Override
                 public void onReceive(Context context, Intent intent) {
 
+                    // When a package is replaced we will receive two intents, one representing the
+                    // removal of the old package and one representing the addition of the new
+                    // package. We here ignore the intent representing the removed package to make
+                    // sure we don't change WebView provider twice.
+                    if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)
+                            && intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) {
+                        return;
+                    }
+
                     for (String packageName : WebViewFactory.getWebViewPackageNames()) {
                         String webviewPackage = "package:" + packageName;
 
@@ -73,7 +82,8 @@
                 }
         };
         IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         filter.addDataScheme("package");
         getContext().registerReceiver(mWebViewUpdatedReceiver, filter);
 
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index 85a12db..4b3620f 100644
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -70,7 +70,7 @@
         /** Returns the display info. of the dim layer user. */
         DisplayInfo getDisplayInfo();
         /** Gets the bounds of the dim layer user. */
-        void getBounds(Rect outBounds);
+        void getDimBounds(Rect outBounds);
         String toShortString();
     }
     /** The user of this dim layer. */
diff --git a/services/core/java/com/android/server/wm/DimLayerController.java b/services/core/java/com/android/server/wm/DimLayerController.java
index 921d27c..bd30bd5 100644
--- a/services/core/java/com/android/server/wm/DimLayerController.java
+++ b/services/core/java/com/android/server/wm/DimLayerController.java
@@ -43,7 +43,7 @@
 
     /** Updates the dim layer bounds, recreating it if needed. */
     void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
-        DimLayerState state = getOrCreateDimLayerState(dimLayerUser, false);
+        DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
         final boolean previousFullscreen = state.dimLayer != null
                 && state.dimLayer == mSharedFullScreenDimLayer;
         DimLayer newDimLayer;
@@ -63,7 +63,7 @@
                     // Create new full screen dim layer.
                     newDimLayer = new DimLayer(mDisplayContent.mService, dimLayerUser, displayId);
                 }
-                dimLayerUser.getBounds(mTmpBounds);
+                dimLayerUser.getDimBounds(mTmpBounds);
                 newDimLayer.setBounds(mTmpBounds);
                 mSharedFullScreenDimLayer = newDimLayer;
             } else if (state.dimLayer != null) {
@@ -73,14 +73,13 @@
             newDimLayer = (state.dimLayer == null || previousFullscreen)
                     ? new DimLayer(mDisplayContent.mService, dimLayerUser, displayId)
                     : state.dimLayer;
-            dimLayerUser.getBounds(mTmpBounds);
+            dimLayerUser.getDimBounds(mTmpBounds);
             newDimLayer.setBounds(mTmpBounds);
         }
         state.dimLayer = newDimLayer;
     }
 
-    private DimLayerState getOrCreateDimLayerState(
-            DimLayer.DimLayerUser dimLayerUser, boolean aboveApp) {
+    private DimLayerState getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser) {
         if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser="
                 + dimLayerUser.toShortString());
         DimLayerState state = mState.get(dimLayerUser);
@@ -88,7 +87,6 @@
             state = new DimLayerState();
             mState.put(dimLayerUser, state);
         }
-        state.dimAbove = aboveApp;
         return state;
     }
 
@@ -127,7 +125,8 @@
             WindowStateAnimator newWinAnimator, boolean aboveApp) {
         // Only set dim params on the highest dimmed layer.
         // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer.
-        DimLayerState state = getOrCreateDimLayerState(dimLayerUser, aboveApp);
+        DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
+        state.dimAbove = aboveApp;
         if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
                 + " dimLayerUser=" + dimLayerUser.toShortString()
                 + " newWinAnimator=" + newWinAnimator
@@ -161,7 +160,7 @@
                 + " state.dimLayer.isDimming=" + state.dimLayer.isDimming());
         if (!state.continueDimming && state.dimLayer.isDimming()) {
             state.animator = null;
-            dimLayerUser.getBounds(mTmpBounds);
+            dimLayerUser.getDimBounds(mTmpBounds);
             state.dimLayer.setBounds(mTmpBounds);
         }
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8c00a57..e264c43 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -291,16 +291,18 @@
             final ArrayList<Task> tasks = mStacks.get(stackNdx).getTasks();
             for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
                 final Task task = tasks.get(taskNdx);
-                // We need to use the visible frame on the window for any touch-related tests.
-                // Can't use the task's bounds because the original task bounds might be adjusted
-                // to fit the content frame. For example, the presence of the IME adjusting the
+                final WindowState win = task.getTopVisibleAppMainWindow();
+                if (win == null) {
+                    continue;
+                }
+                // We need to use the task's dim bounds (which is derived from the visible
+                // bounds of its apps windows) for any touch-related tests. Can't use
+                // the task's original bounds because it might be adjusted to fit the
+                // content frame. For example, the presence of the IME adjusting the
                 // windows frames when the app window is the IME target.
-                final WindowState win = task.getTopAppMainWindow();
-                if (win != null) {
-                    win.getVisibleBounds(mTmpRect, !BOUNDS_FOR_TOUCH);
-                    if (mTmpRect.contains(x, y)) {
-                        return task.mTaskId;
-                    }
+                task.getDimBounds(mTmpRect);
+                if (mTmpRect.contains(x, y)) {
+                    return task.mTaskId;
                 }
             }
         }
@@ -308,10 +310,10 @@
     }
 
     /**
-     * Find the window whose outside touch area (for resizing) (x, y) falls within.
+     * Find the task whose outside touch area (for resizing) (x, y) falls within.
      * Returns null if the touch doesn't fall into a resizing area.
      */
-    WindowState findWindowForControlPoint(int x, int y) {
+    Task findTaskForControlPoint(int x, int y) {
         final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             TaskStack stack = mStacks.get(stackNdx);
@@ -325,24 +327,22 @@
                     return null;
                 }
 
-                // We need to use the visible frame on the window for any touch-related
-                // tests. Can't use the task's bounds because the original task bounds
-                // might be adjusted to fit the content frame. (One example is when the
-                // task is put to top-left quadrant, the actual visible frame would not
-                // start at (0,0) after it's adjusted for the status bar.)
-                final WindowState win = task.getTopAppMainWindow();
-                if (win != null) {
-                    win.getVisibleBounds(mTmpRect, !BOUNDS_FOR_TOUCH);
-                    mTmpRect.inset(-delta, -delta);
-                    if (mTmpRect.contains(x, y)) {
-                        mTmpRect.inset(delta, delta);
-                        if (!mTmpRect.contains(x, y)) {
-                            return win;
-                        }
-                        // User touched inside the task. No need to look further,
-                        // focus transfer will be handled in ACTION_UP.
-                        return null;
+                // We need to use the task's dim bounds (which is derived from the visible
+                // bounds of its apps windows) for any touch-related tests. Can't use
+                // the task's original bounds because it might be adjusted to fit the
+                // content frame. One example is when the task is put to top-left quadrant,
+                // the actual visible area would not start at (0,0) after it's adjusted
+                // for the status bar.
+                task.getDimBounds(mTmpRect);
+                mTmpRect.inset(-delta, -delta);
+                if (mTmpRect.contains(x, y)) {
+                    mTmpRect.inset(delta, delta);
+                    if (!mTmpRect.contains(x, y)) {
+                        return task;
                     }
+                    // User touched inside the task. No need to look further,
+                    // focus transfer will be handled in ACTION_UP.
+                    return null;
                 }
             }
         }
@@ -351,12 +351,18 @@
 
     void setTouchExcludeRegion(Task focusedTask) {
         mTouchExcludeRegion.set(mBaseDisplayRect);
-        WindowList windows = getWindowList();
         final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
-        for (int i = windows.size() - 1; i >= 0; --i) {
-            final WindowState win = windows.get(i);
-            final Task task = win.getTask();
-            if (win.isVisibleLw() && task != null) {
+        boolean addBackFocusedTask = false;
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            TaskStack stack = mStacks.get(stackNdx);
+            final ArrayList<Task> tasks = stack.getTasks();
+            for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+                final Task task = tasks.get(taskNdx);
+                final WindowState win = task.getTopVisibleAppMainWindow();
+                if (win == null) {
+                    continue;
+                }
+
                 /**
                  * Exclusion region is the region that TapDetector doesn't care about.
                  * Here we want to remove all non-focused tasks from the exclusion region.
@@ -368,13 +374,17 @@
                  */
                 final boolean isFreeformed = task.inFreeformWorkspace();
                 if (task != focusedTask || isFreeformed) {
-                    mTmpRect.set(win.mVisibleFrame);
-                    mTmpRect.intersect(win.mVisibleInsets);
-                    /**
-                     * If the task is freeformed, enlarge the area to account for outside
-                     * touch area for resize.
-                     */
+                    task.getDimBounds(mTmpRect);
                     if (isFreeformed) {
+                        // If we're removing a freeform, focused app from the exclusion region,
+                        // we need to add back its touchable frame later. Remember the touchable
+                        // frame now.
+                        if (task == focusedTask) {
+                            addBackFocusedTask = true;
+                            mTmpRect2.set(mTmpRect);
+                        }
+                        // If the task is freeformed, enlarge the area to account for outside
+                        // touch area for resize.
                         mTmpRect.inset(-delta, -delta);
                         // Intersect with display content rect. If we have system decor (status bar/
                         // navigation bar), we want to exclude that from the tap detection.
@@ -385,17 +395,14 @@
                     }
                     mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
                 }
-                /**
-                 * If we removed the focused task above, add it back and only leave its
-                 * outside touch area in the exclusion. TapDectector is not interested in
-                 * any touch inside the focused task itself.
-                 */
-                if (task == focusedTask && isFreeformed) {
-                    mTmpRect.inset(delta, delta);
-                    mTouchExcludeRegion.op(mTmpRect, Region.Op.UNION);
-                }
             }
         }
+        // If we removed the focused task above, add it back and only leave its
+        // outside touch area in the exclusion. TapDectector is not interested in
+        // any touch inside the focused task itself.
+        if (addBackFocusedTask) {
+            mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION);
+        }
         if (mTapDetector != null) {
             mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion);
         }
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index c1e0481..6b62467 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -87,7 +87,7 @@
             frame.set(mLastRect);
             return;
         } else {
-            stack.getBounds(mTmpRect);
+            stack.getDimBounds(mTmpRect);
         }
         int side = stack.getDockSide();
         switch (side) {
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 400cc5e..2be7ab8 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -435,7 +435,7 @@
                 continue;
             }
 
-            child.getVisibleBounds(mTmpRect, !BOUNDS_FOR_TOUCH);
+            child.getVisibleBounds(mTmpRect);
             if (!mTmpRect.contains(x, y)) {
                 // outside of this window's activity stack == don't tell about drags
                 continue;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7c92684..46cd7cd 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -253,8 +253,7 @@
         return false;
     }
 
-    /** Bounds of the task with other system factors taken into consideration. */
-    @Override
+    /** Original bounds of the task if applicable, otherwise fullscreen rect. */
     public void getBounds(Rect out) {
         if (useCurrentBounds()) {
             // No need to adjust the output bounds if fullscreen or the docked stack is visible
@@ -269,6 +268,70 @@
         mStack.getDisplayContent().getLogicalDisplayRect(out);
     }
 
+
+    /**
+     * Calculate the maximum visible area of this task. If the task has only one app,
+     * the result will be visible frame of that app. If the task has more than one apps,
+     * we search from top down if the next app got different visible area.
+     *
+     * This effort is to handle the case where some task (eg. GMail composer) might pop up
+     * a dialog that's different in size from the activity below, in which case we should
+     * be dimming the entire task area behind the dialog.
+     *
+     * @param out Rect containing the max visible bounds.
+     * @return true if the task has some visible app windows; false otherwise.
+     */
+    boolean getMaxVisibleBounds(Rect out) {
+        boolean foundTop = false;
+        for (int i = mAppTokens.size() - 1; i >= 0; i--) {
+            final AppWindowToken token = mAppTokens.get(i);
+            // skip hidden (or about to hide) apps
+            if (token.mIsExiting || token.clientHidden || token.hiddenRequested) {
+                continue;
+            }
+            final WindowState win = token.findMainWindow();
+            if (win == null) {
+                continue;
+            }
+            if (!foundTop) {
+                out.set(win.mVisibleFrame);
+                foundTop = true;
+                continue;
+            }
+            if (win.mVisibleFrame.left < out.left) {
+                out.left = win.mVisibleFrame.left;
+            }
+            if (win.mVisibleFrame.top < out.top) {
+                out.top = win.mVisibleFrame.top;
+            }
+            if (win.mVisibleFrame.right > out.right) {
+                out.right = win.mVisibleFrame.right;
+            }
+            if (win.mVisibleFrame.bottom > out.bottom) {
+                out.bottom = win.mVisibleFrame.bottom;
+            }
+        }
+        return foundTop;
+    }
+
+    /** Bounds of the task to be used for dimming, as well as touch related tests. */
+    @Override
+    public void getDimBounds(Rect out) {
+        if (useCurrentBounds()) {
+            if (inFreeformWorkspace() && getMaxVisibleBounds(out)) {
+                return;
+            }
+
+            out.set(mBounds);
+            return;
+        }
+
+        // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
+        // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
+        // system.
+        mStack.getDisplayContent().getLogicalDisplayRect(out);
+    }
+
     void setDragResizing(boolean dragResizing) {
         mDragResizing = dragResizing;
     }
@@ -355,14 +418,20 @@
         return mStack != null && mStack.mStackId == DOCKED_STACK_ID;
     }
 
-    WindowState getTopAppMainWindow() {
-        final int tokensCount = mAppTokens.size();
-        return tokensCount > 0 ? mAppTokens.get(tokensCount - 1).findMainWindow() : null;
+    WindowState getTopVisibleAppMainWindow() {
+        final AppWindowToken token = getTopVisibleAppToken();
+        return token != null ? token.findMainWindow() : null;
     }
 
-    AppWindowToken getTopAppWindowToken() {
-        final int tokensCount = mAppTokens.size();
-        return tokensCount > 0 ? mAppTokens.get(tokensCount - 1) : null;
+    AppWindowToken getTopVisibleAppToken() {
+        for (int i = mAppTokens.size() - 1; i >= 0; i--) {
+            final AppWindowToken token = mAppTokens.get(i);
+            // skip hidden (or about to hide) apps
+            if (!token.mIsExiting && !token.clientHidden && !token.hiddenRequested) {
+                return token;
+            }
+        }
+        return null;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index aae3bd2..f5e4e3b 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -149,7 +149,7 @@
                         }
                         synchronized (mService.mWindowMap) {
                             mDragEnded = notifyMoveLocked(newX, newY);
-                            mTask.getBounds(mTmpRect);
+                            mTask.getDimBounds(mTmpRect);
                         }
                         if (!mTmpRect.equals(mWindowDragBounds)) {
                             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
@@ -332,28 +332,34 @@
                 + ", {" + startX + ", " + startY + "}");
         }
         mCtrlType = CTRL_NONE;
+        mTask = win.getTask();
+        mStartDragX = startX;
+        mStartDragY = startY;
+
+        // Use the dim bounds, not the original task bounds. The cursor
+        // movement should be calculated relative to the visible bounds.
+        // Also, use the dim bounds of the task which accounts for
+        // multiple app windows. Don't use any bounds from win itself as it
+        // may not be the same size as the task.
+        mTask.getDimBounds(mTmpRect);
+
         if (resize) {
-            final Rect visibleFrame = win.mVisibleFrame;
-            if (startX < visibleFrame.left) {
+            if (startX < mTmpRect.left) {
                 mCtrlType |= CTRL_LEFT;
             }
-            if (startX > visibleFrame.right) {
+            if (startX > mTmpRect.right) {
                 mCtrlType |= CTRL_RIGHT;
             }
-            if (startY < visibleFrame.top) {
+            if (startY < mTmpRect.top) {
                 mCtrlType |= CTRL_TOP;
             }
-            if (startY > visibleFrame.bottom) {
+            if (startY > mTmpRect.bottom) {
                 mCtrlType |= CTRL_BOTTOM;
             }
             mResizing = true;
         }
 
-        mTask = win.getTask();
-        mStartDragX = startX;
-        mStartDragY = startY;
-
-        mService.getTaskBounds(mTask.mTaskId, mWindowOriginalBounds);
+        mWindowOriginalBounds.set(mTmpRect);
     }
 
     private void endDragLocked() {
@@ -393,7 +399,7 @@
         }
 
         // This is a moving operation.
-        mTask.mStack.getBounds(mTmpRect);
+        mTask.mStack.getDimBounds(mTmpRect);
         mTmpRect.inset(mMinVisibleWidth, mMinVisibleHeight);
         if (!mTmpRect.contains((int) x, (int) y)) {
             // We end the moving operation if position is outside the stack bounds.
@@ -438,7 +444,7 @@
             return CTRL_NONE;
         }
 
-        mTask.mStack.getBounds(mTmpRect);
+        mTask.mStack.getDimBounds(mTmpRect);
         if (x - mSideMargin <= mTmpRect.left) {
             return CTRL_LEFT;
         }
@@ -450,7 +456,7 @@
     }
 
     private void showDimLayer() {
-        mTask.mStack.getBounds(mTmpRect);
+        mTask.mStack.getDimBounds(mTmpRect);
         if (mCurrentDimSide == CTRL_LEFT) {
             mTmpRect.right = mTmpRect.centerX();
         } else if (mCurrentDimSide == CTRL_RIGHT) {
@@ -473,7 +479,7 @@
     }
 
     @Override
-    public void getBounds(Rect out) {
+    public void getDimBounds(Rect out) {
         // This dim layer user doesn't need this.
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 7c02b43..8085f13 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -189,8 +189,6 @@
         return false;
     }
 
-    /** Bounds of the stack with other system factors taken into consideration. */
-    @Override
     public void getBounds(Rect out) {
         if (useCurrentBounds()) {
             // No need to adjust the output bounds if fullscreen or the docked stack is visible
@@ -205,6 +203,12 @@
         mDisplayContent.getLogicalDisplayRect(out);
     }
 
+    /** Bounds of the stack with other system factors taken into consideration. */
+    @Override
+    public void getDimBounds(Rect out) {
+        getBounds(out);
+    }
+
     void updateDisplayInfo(Rect bounds) {
         if (mDisplayContent != null) {
             for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index a33fb13..f5b83bb 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -90,11 +90,11 @@
             case MotionEvent.ACTION_HOVER_MOVE: {
                 final int x = (int) motionEvent.getX();
                 final int y = (int) motionEvent.getY();
-                final WindowState window = mDisplayContent.findWindowForControlPoint(x, y);
-                if (window == null) {
+                final Task task = mDisplayContent.findTaskForControlPoint(x, y);
+                if (task == null) {
                     break;
                 }
-                window.getVisibleBounds(mTmpRect, false);
+                task.getDimBounds(mTmpRect);
                 if (!mTmpRect.isEmpty() && !mTmpRect.contains(x, y)) {
                     int iconShape = STYLE_DEFAULT;
                     if (x < mTmpRect.left) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 38bd71d..a96bd2c 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -67,7 +67,7 @@
     private final WindowSurfacePlacer mWindowPlacerLocked;
 
     /** Is any window animating? */
-    boolean mAnimating;
+    private boolean mAnimating;
 
     /** Is any app window animating? */
     boolean mAppWindowAnimating;
@@ -168,7 +168,8 @@
                     appAnimator.wasAnimating = appAnimator.animating;
                     if (appAnimator.stepAnimationLocked(mCurrentTime, displayId)) {
                         appAnimator.animating = true;
-                        mAnimating = mAppWindowAnimating = true;
+                        setAnimating(true);
+                        mAppWindowAnimating = true;
                     } else if (appAnimator.wasAnimating) {
                         // stopped animating, do one more pass through the layout
                         setAppLayoutChanges(appAnimator,
@@ -186,7 +187,8 @@
                 final AppWindowAnimator appAnimator = exitingAppTokens.get(i).mAppAnimator;
                 appAnimator.wasAnimating = appAnimator.animating;
                 if (appAnimator.stepAnimationLocked(mCurrentTime, displayId)) {
-                    mAnimating = mAppWindowAnimating = true;
+                    setAnimating(true);
+                    mAppWindowAnimating = true;
                 } else if (appAnimator.wasAnimating) {
                     // stopped animating, do one more pass through the layout
                     setAppLayoutChanges(appAnimator,
@@ -282,7 +284,7 @@
                 final boolean wasAnimating = winAnimator.mWasAnimating;
                 final boolean nowAnimating = winAnimator.stepAnimationLocked(mCurrentTime);
                 winAnimator.mWasAnimating = nowAnimating;
-                mAnimating |= nowAnimating;
+                orAnimating(nowAnimating);
 
                 if (DEBUG_WALLPAPER) {
                     Slog.v(TAG, win + ": wasAnimating=" + wasAnimating +
@@ -546,7 +548,7 @@
                         }
                     }
                 }
-                mAnimating = true;
+                setAnimating(true);
             }
 
             // If this window's app token is running a detached wallpaper
@@ -617,7 +619,7 @@
 
                             // We can now show all of the drawn windows!
                             if (!mService.mOpeningApps.contains(wtoken)) {
-                                mAnimating |= appAnimator.showAllWindowsLocked();
+                                orAnimating(appAnimator.showAllWindowsLocked());
                             }
                         }
                     }
@@ -636,7 +638,7 @@
         mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
         mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
         boolean wasAnimating = mAnimating;
-        mAnimating = false;
+        setAnimating(false);
         mAppWindowAnimating = false;
         if (DEBUG_WINDOW_TRACE) {
             Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
@@ -657,7 +659,7 @@
                         displayAnimator.mScreenRotationAnimation;
                 if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
                     if (screenRotationAnimation.stepAnimationLocked(mCurrentTime)) {
-                        mAnimating = true;
+                        setAnimating(true);
                     } else {
                         mBulkUpdateParams |= SET_UPDATE_ROTATION;
                         screenRotationAnimation.kill();
@@ -697,7 +699,7 @@
                     screenRotationAnimation.updateSurfacesInTransaction();
                 }
 
-                mAnimating |= mService.getDisplayContentLocked(displayId).animateDimLayers();
+                orAnimating(mService.getDisplayContentLocked(displayId).animateDimLayers());
 
                 //TODO (multidisplay): Magnification is supported only for the default display.
                 if (mService.mAccessibilityController != null
@@ -920,4 +922,16 @@
     private class DisplayContentsAnimator {
         ScreenRotationAnimation mScreenRotationAnimation = null;
     }
+
+    boolean isAnimating() {
+        return mAnimating;
+    }
+
+    void setAnimating(boolean animating) {
+        mAnimating = animating;
+    }
+
+    void orAnimating(boolean animating) {
+        mAnimating |= animating;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d9af27d..5737d479 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2725,6 +2725,8 @@
                         WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
             }
 
+            final int oldSurfaceResizeGeneration = winAnimator.mSurfaceResizeGeneration;
+
             win.setDisplayLayoutNeeded();
             win.mGivenInsetsPending = (flags&WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
             configChanged = updateOrientationFromAppTokensLocked(false);
@@ -2737,6 +2739,9 @@
             if (win.mAppToken != null) {
                 win.mAppToken.updateReportedVisibilityLocked();
             }
+            if (winAnimator.mSurfaceResizeGeneration != oldSurfaceResizeGeneration) {
+                result |= WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED;
+            }
             outFrame.set(win.mCompatFrame);
             outOverscanInsets.set(win.mOverscanInsets);
             outContentInsets.set(win.mContentInsets);
@@ -5850,7 +5855,7 @@
                         int right = wf.right - cr.right;
                         int bottom = wf.bottom - cr.bottom;
                         frame.union(left, top, right, bottom);
-                        ws.getVisibleBounds(stackBounds, !BOUNDS_FOR_TOUCH);
+                        ws.getVisibleBounds(stackBounds);
                         if (!frame.intersect(stackBounds)) {
                             // Set frame empty if there's no intersection.
                             frame.setEmpty();
@@ -7069,15 +7074,16 @@
     }
 
     private void startResizingTask(DisplayContent displayContent, int startX, int startY) {
-        WindowState win = null;
+        Task task = null;
         synchronized (mWindowMap) {
-            win = displayContent.findWindowForControlPoint(startX, startY);
-            if (win == null || !startPositioningLocked(win, true /*resize*/, startX, startY)) {
+            task = displayContent.findTaskForControlPoint(startX, startY);
+            if (task == null || !startPositioningLocked(
+                    task.getTopVisibleAppMainWindow(), true /*resize*/, startX, startY)) {
                 return;
             }
         }
         try {
-            mActivityManager.setFocusedTask(win.getTask().mTaskId);
+            mActivityManager.setFocusedTask(task.mTaskId);
         } catch(RemoteException e) {}
     }
 
@@ -7682,7 +7688,7 @@
                     synchronized (mWindowMap) {
                         // Since we're holding both mWindowMap and mAnimator we don't need to
                         // hold mAnimator.mLayoutToAnim.
-                        if (mAnimator.mAnimating || mAnimationScheduled) {
+                        if (mAnimator.isAnimating() || mAnimationScheduled) {
                             // If we are animating, don't do the gc now but
                             // delay a bit so we don't interrupt the animation.
                             sendEmptyMessageDelayed(H.FORCE_GC, 2000);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 080a2d1..673c21f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -729,8 +729,18 @@
             mVisibleFrame.set(mContentFrame);
             mStableFrame.set(mContentFrame);
         } else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
-            mDisplayContent.getDockedDividerController().positionDockedStackedDivider(mFrame);
-            mContentFrame.set(mFrame);
+            if (isVisibleLw()) {
+                // We don't adjust the dock divider frame for reasons other than performance. The
+                // real reason is that if it gets adjusted before it is shown for the first time,
+                // it would get size (0, 0). This causes a problem when we finally show the dock
+                // divider and try to draw to it. We do set the surface size at that moment to
+                // the correct size, but it's too late for the Surface Flinger to make it
+                // available for view rendering and as a result the renderer receives size 1, 1.
+                // This way we just keep the divider at the original size and Surface Flinger
+                // will return the correct value to the renderer.
+                mDisplayContent.getDockedDividerController().positionDockedStackedDivider(mFrame);
+                mContentFrame.set(mFrame);
+            }
         } else {
             mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
                     Math.max(mContentFrame.top, mFrame.top),
@@ -962,17 +972,15 @@
     /**
      * Retrieves the visible bounds of the window.
      * @param bounds The rect which gets the bounds.
-     * @param forTouch Pass in BOUNDS_FOR_TOUCH to get touch related bounds, otherwise visible
-     *        bounds will be returned.
      */
-    void getVisibleBounds(Rect bounds, boolean forTouch) {
+    void getVisibleBounds(Rect bounds) {
         boolean intersectWithStackBounds = mAppToken != null && mAppToken.mCropWindowsToStack;
         bounds.setEmpty();
         mTmpRect.setEmpty();
         if (intersectWithStackBounds) {
             final TaskStack stack = getStack();
             if (stack != null) {
-                stack.getBounds(mTmpRect);
+                stack.getDimBounds(mTmpRect);
             } else {
                 intersectWithStackBounds = false;
             }
@@ -990,12 +998,6 @@
             }
             return;
         }
-        if (forTouch && inFreeformWorkspace()) {
-            final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
-            final int delta = WindowManagerService.dipToPixel(
-                    RESIZE_HANDLE_WIDTH_IN_DP, displayMetrics);
-            bounds.inset(-delta, -delta);
-        }
     }
 
     public long getInputDispatchingTimeoutNanos() {
@@ -1415,22 +1417,24 @@
         if (modal && mAppToken != null) {
             // Limit the outer touch to the activity stack region.
             flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-            if (!inFreeformWorkspace()) {
-                // If this is a modal window we need to dismiss it if it's not full screen and the
-                // touch happens outside of the frame that displays the content. This means we
-                // need to intercept touches outside of that window. The dim layer user
-                // associated with the window (task or stack) will give us the good bounds, as
-                // they would be used to display the dim layer.
-                final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
-                if (dimLayerUser != null) {
-                    dimLayerUser.getBounds(mTmpRect);
-                } else {
-                    getVisibleBounds(mTmpRect, BOUNDS_FOR_TOUCH);
-                }
+            // If this is a modal window we need to dismiss it if it's not full screen and the
+            // touch happens outside of the frame that displays the content. This means we
+            // need to intercept touches outside of that window. The dim layer user
+            // associated with the window (task or stack) will give us the good bounds, as
+            // they would be used to display the dim layer.
+            final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
+            if (dimLayerUser != null) {
+                dimLayerUser.getDimBounds(mTmpRect);
             } else {
+                getVisibleBounds(mTmpRect);
+            }
+            if (inFreeformWorkspace()) {
                 // For freeform windows we the touch region to include the whole surface for the
                 // shadows.
-                getVisibleBounds(mTmpRect, BOUNDS_FOR_TOUCH);
+                final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
+                final int delta = WindowManagerService.dipToPixel(
+                        RESIZE_HANDLE_WIDTH_IN_DP, displayMetrics);
+                mTmpRect.inset(-delta, -delta);
             }
             region.set(mTmpRect);
         } else {
@@ -1704,7 +1708,7 @@
 
         Task task = getTask();
         if (task == null || task.inHomeStack()
-                || task.getTopAppWindowToken() != mAppToken) {
+                || task.getTopVisibleAppToken() != mAppToken) {
             // Don't save surfaces for home stack apps. These usually resume and draw
             // first frame very fast. Saving surfaces are mostly a waste of memory.
             // Don't save if the window is not the topmost window.
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 10f737f..5f50144 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,7 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerService.DEBUG_LAYERS;
@@ -24,12 +26,12 @@
 import static com.android.server.wm.WindowManagerService.DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.WindowManagerService.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerService.localLOGV;
 import static com.android.server.wm.WindowManagerService.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerService.SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.WindowManagerService.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
-import static com.android.server.wm.WindowState.*;
+import static com.android.server.wm.WindowManagerService.localLOGV;
+import static com.android.server.wm.WindowState.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
 import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN;
 
@@ -37,7 +39,6 @@
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Debug;
@@ -47,12 +48,10 @@
 import android.view.DisplayInfo;
 import android.view.MagnificationSpec;
 import android.view.Surface.OutOfResourcesException;
-import android.view.Surface;
 import android.view.SurfaceControl;
-import android.view.SurfaceSession;
 import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
 import android.view.WindowManager.LayoutParams;
+import android.view.WindowManagerPolicy;
 import android.view.animation.Animation;
 import android.view.animation.AnimationSet;
 import android.view.animation.AnimationUtils;
@@ -61,7 +60,6 @@
 import com.android.server.wm.WindowManagerService.H;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 
 /**
  * Keep track of animations and surface operations for a single WindowState.
@@ -101,6 +99,7 @@
      * we must tell them application to resize (and thus redraw itself).
      */
     boolean mSurfaceResized;
+    int mSurfaceResizeGeneration;
     WindowSurfaceController mSurfaceController;
     private WindowSurfaceController mPendingDestroySurface;
 
@@ -183,6 +182,8 @@
 
     int mAttrType;
 
+    private final Rect mTmpSize = new Rect();
+
     WindowStateAnimator(final WindowState win) {
         final WindowManagerService service = win.mService;
 
@@ -442,7 +443,7 @@
         if (!isWindowAnimating()) {
             //TODO (multidisplay): Accessibility is supported only for the default display.
             if (mService.mAccessibilityController != null
-                    && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                    && mWin.getDisplayId() == DEFAULT_DISPLAY) {
                 mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
             }
         }
@@ -581,52 +582,16 @@
                 flags |= SurfaceControl.SECURE;
             }
 
-            float left = w.mFrame.left + w.mXOffset;
-            float top = w.mFrame.top + w.mYOffset;
-
-            int width;
-            int height;
-            if ((attrs.flags & LayoutParams.FLAG_SCALED) != 0) {
-                // for a scaled surface, we always want the requested
-                // size.
-                width = w.mRequestedWidth;
-                height = w.mRequestedHeight;
-            } else {
-                // When we're doing a drag-resizing, request a surface that's fullscreen size,
-                // 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.isDragResizing()) {
-                    left = 0;
-                    top = 0;
-                    width = displayInfo.logicalWidth;
-                    height = displayInfo.logicalHeight;
-                } else {
-                    width = w.mCompatFrame.width();
-                    height = w.mCompatFrame.height();
-                }
-            }
-
-            // Something is wrong and SurfaceFlinger will not like this,
-            // try to revert to sane values
-            if (width <= 0) {
-                width = 1;
-            }
-            if (height <= 0) {
-                height = 1;
-            }
-
-            // Adjust for surface insets.
-            width += attrs.surfaceInsets.left + attrs.surfaceInsets.right;
-            height += attrs.surfaceInsets.top + attrs.surfaceInsets.bottom;
-            left -= attrs.surfaceInsets.left;
-            top -= attrs.surfaceInsets.top;
+            mTmpSize.set(w.mFrame.left + w.mXOffset, w.mFrame.top + w.mYOffset, 0, 0);
+            calculateSurfaceBounds(w, attrs);
+            final int width = mTmpSize.width();
+            final int height = mTmpSize.height();
 
             if (DEBUG_VISIBILITY) {
                 Slog.v(TAG, "Creating surface in session "
                         + mSession.mSurfaceSession + " window " + this
                         + " w=" + width + " h=" + height
-                        + " x=" + left + " y=" + top
+                        + " x=" + mTmpSize.left + " y=" + mTmpSize.top
                         + " format=" + attrs.format + " flags=" + flags);
             }
 
@@ -692,15 +657,15 @@
                 Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked");
                 WindowManagerService.logSurface(w, "CREATE pos=("
                         + w.mFrame.left + "," + w.mFrame.top + ") ("
-                        + w.mCompatFrame.width() + "x" + w.mCompatFrame.height()
-                        + "), layer=" + mAnimLayer + " HIDE", null);
+                        + width + "x" + height + "), layer=" + mAnimLayer + " HIDE", null);
             }
 
             // Start a new transaction and apply position & offset.
             final int layerStack = w.getDisplayContent().getDisplay().getLayerStack();
             if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
-                    "POS " + left + ", " + top, null);
-            mSurfaceController.setPositionAndLayer(left, top, layerStack, mAnimLayer);
+                    "POS " + mTmpSize.left + ", " + mTmpSize.top, null);
+            mSurfaceController.setPositionAndLayer(mTmpSize.left, mTmpSize.top, layerStack,
+                    mAnimLayer);
             mLastHidden = true;
 
             if (WindowManagerService.localLOGV) Slog.v(
@@ -709,6 +674,57 @@
         return mSurfaceController;
     }
 
+    private void calculateSurfaceBounds(WindowState w, LayoutParams attrs) {
+        if ((attrs.flags & FLAG_SCALED) != 0) {
+            // For a scaled surface, we always want the requested size.
+            mTmpSize.right = mTmpSize.left + w.mRequestedWidth;
+            mTmpSize.bottom = mTmpSize.top + w.mRequestedHeight;
+        } else {
+            // When we're doing a drag-resizing, request a surface that's fullscreen size,
+            // so that we don't need to reallocate during the process. This also prevents
+            // buffer drops due to size mismatch.
+            if (w.isDragResizing()) {
+                if (w.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM) {
+                    mTmpSize.left = 0;
+                    mTmpSize.top = 0;
+                }
+                final DisplayInfo displayInfo = w.getDisplayInfo();
+                mTmpSize.right = mTmpSize.left + displayInfo.logicalWidth;
+                mTmpSize.bottom = mTmpSize.top + displayInfo.logicalHeight;
+            } else {
+                mTmpSize.right = mTmpSize.left + w.mCompatFrame.width();
+                mTmpSize.bottom = mTmpSize.top + w.mCompatFrame.height();
+            }
+        }
+
+        // Something is wrong and SurfaceFlinger will not like this, try to revert to sane values.
+        if (mTmpSize.width() < 1) {
+            Slog.w(TAG, "Width of " + w + " is not positive " + mTmpSize.width());
+            mTmpSize.right = mTmpSize.left + 1;
+        }
+        if (mTmpSize.height() < 1) {
+            Slog.w(TAG, "Height of " + w + " is not positive " + mTmpSize.height());
+            mTmpSize.bottom = mTmpSize.top + 1;
+        }
+
+        final int displayId = w.getDisplayId();
+        float scale = 1.0f;
+        // Magnification is supported only for the default display.
+        if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
+            final MagnificationSpec spec =
+                    mService.mAccessibilityController.getMagnificationSpecForWindowLocked(w);
+            if (spec != null && !spec.isNop()) {
+                scale = spec.scale;
+            }
+        }
+
+        // Adjust for surface insets.
+        mTmpSize.left -= scale * attrs.surfaceInsets.left;
+        mTmpSize.top -= scale * attrs.surfaceInsets.top;
+        mTmpSize.right += scale * (attrs.surfaceInsets.left + attrs.surfaceInsets.right);
+        mTmpSize.bottom += scale * (attrs.surfaceInsets.top + attrs.surfaceInsets.bottom);
+    }
+
     void destroySurfaceLocked() {
         final AppWindowToken wtoken = mWin.mAppToken;
         if (wtoken != null) {
@@ -891,7 +907,7 @@
             tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
 
             //TODO (multidisplay): Magnification is supported only for the default display.
-            if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) {
+            if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
                 MagnificationSpec spec = mService.mAccessibilityController
                         .getMagnificationSpecForWindowLocked(mWin);
                 if (spec != null && !spec.isNop()) {
@@ -974,7 +990,7 @@
 
         MagnificationSpec spec = null;
         //TODO (multidisplay): Magnification is supported only for the default display.
-        if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) {
+        if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
             spec = mService.mAccessibilityController.getMagnificationSpecForWindowLocked(mWin);
         }
         if (spec != null) {
@@ -1069,7 +1085,7 @@
         } else if (w.mDecorFrame.isEmpty()) {
             // Windows without policy decor aren't cropped.
             w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
-        } else if (w.mAttrs.type == LayoutParams.TYPE_WALLPAPER && mAnimator.mAnimating) {
+        } else if (w.mAttrs.type == LayoutParams.TYPE_WALLPAPER && mAnimator.isAnimating()) {
             // If we're animating, the wallpaper crop should only be updated at the end of the
             // animation.
             mTmpClipRect.set(w.mSystemDecorRect);
@@ -1109,12 +1125,8 @@
         // The clip rect was generated assuming (0,0) as the window origin,
         // so we need to translate to match the actual surface coordinates.
         clipRect.offset(attrs.surfaceInsets.left, attrs.surfaceInsets.top);
-        // We don't want to clip to stack bounds windows that are currently doing entrance
-        // animation for docked window, otherwise the animating window will be suddenly cut off.
 
-        if (!(mAnimator.mAnimating && w.inDockedWorkspace())) {
-            adjustCropToStackBounds(w, clipRect, isFreeformResizing);
-        }
+        adjustCropToStackBounds(w, clipRect, isFreeformResizing);
 
         w.transformFromScreenToSurfaceSpace(clipRect);
 
@@ -1127,100 +1139,57 @@
     private void adjustCropToStackBounds(WindowState w, Rect clipRect, boolean isFreeformResizing) {
         final AppWindowToken appToken = w.mAppToken;
         final Task task = w.getTask();
-        // We don't apply the the stack bounds to the window that is being replaced, because it was
-        // living in a different stack. If we suddenly crop it to the new stack bounds, it might
-        // get cut off. We don't want it to happen, so we let it ignore the stack bounds until it
-        // gets removed. The window that will replace it will abide them.
-        if (task != null && appToken.mCropWindowsToStack && !appToken.mWillReplaceWindow) {
-            TaskStack stack = task.mStack;
-            stack.getBounds(mTmpStackBounds);
-            // When we resize we use the big surface approach, which means we can't trust the
-            // window frame bounds anymore. Instead, the window will be placed at 0, 0, but to avoid
-            // hardcoding it, we use surface coordinates.
-            final int frameX = isFreeformResizing ? (int) mSurfaceController.getX() :
-                    w.mFrame.left + mWin.mXOffset - w.getAttrs().surfaceInsets.left;
-            final int frameY = isFreeformResizing ? (int) mSurfaceController.getY() :
-                    w.mFrame.top + mWin.mYOffset - w.getAttrs().surfaceInsets.top;
-            // We need to do some acrobatics with surface position, because their clip region is
-            // relative to the inside of the surface, but the stack bounds aren't.
-            clipRect.left = Math.max(0,
-                    Math.max(mTmpStackBounds.left, frameX + clipRect.left) - frameX);
-            clipRect.top = Math.max(0,
-                    Math.max(mTmpStackBounds.top, frameY + clipRect.top) - frameY);
-            clipRect.right = Math.max(0,
-                    Math.min(mTmpStackBounds.right, frameX + clipRect.right) - frameX);
-            clipRect.bottom = Math.max(0,
-                    Math.min(mTmpStackBounds.bottom, frameY + clipRect.bottom) - frameY);
+        if (task == null || !appToken.mCropWindowsToStack) {
+            return;
         }
+
+        // We don't apply the stack bounds crop if:
+        // 1. The window is currently animating docked mode, otherwise the animating window will be
+        // suddenly cut off.
+        // 2. The window that is being replaced during animation, because it was living in a
+        // different stack. If we suddenly crop it to the new stack bounds, it might get cut off.
+        // We don't want it to happen, so we let it ignore the stack bounds until it gets removed.
+        // The window that will replace it will abide them.
+        if (isAnimating() && (appToken.mWillReplaceWindow || w.inDockedWorkspace())) {
+            return;
+        }
+
+        final TaskStack stack = task.mStack;
+        stack.getDimBounds(mTmpStackBounds);
+        // When we resize we use the big surface approach, which means we can't trust the
+        // window frame bounds anymore. Instead, the window will be placed at 0, 0, but to avoid
+        // hardcoding it, we use surface coordinates.
+        final int frameX = isFreeformResizing ? (int) mSurfaceController.getX() :
+                w.mFrame.left + mWin.mXOffset - w.getAttrs().surfaceInsets.left;
+        final int frameY = isFreeformResizing ? (int) mSurfaceController.getY() :
+                w.mFrame.top + mWin.mYOffset - w.getAttrs().surfaceInsets.top;
+        // We need to do some acrobatics with surface position, because their clip region is
+        // relative to the inside of the surface, but the stack bounds aren't.
+        clipRect.left = Math.max(0,
+                Math.max(mTmpStackBounds.left, frameX + clipRect.left) - frameX);
+        clipRect.top = Math.max(0,
+                Math.max(mTmpStackBounds.top, frameY + clipRect.top) - frameY);
+        clipRect.right = Math.max(0,
+                Math.min(mTmpStackBounds.right, frameX + clipRect.right) - frameX);
+        clipRect.bottom = Math.max(0,
+                Math.min(mTmpStackBounds.bottom, frameY + clipRect.bottom) - frameY);
     }
 
     void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
         final WindowState w = mWin;
 
-        float left = w.mShownPosition.x;
-        float top = w.mShownPosition.y;
+        mTmpSize.set(w.mShownPosition.x, w.mShownPosition.y, 0, 0);
+        calculateSurfaceBounds(w, w.getAttrs());
 
-        int width;
-        int height;
-        if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
-            // for a scaled surface, we always want the requested
-            // size.
-            width  = w.mRequestedWidth;
-            height = w.mRequestedHeight;
-        } else {
-            // When we're doing a drag-resizing, request a surface that's fullscreen size,
-            // 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();
-
-            // In freeform resize mode, put surface at 0/0.
-            if (w.isDragResizing() && w.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM) {
-                left = 0;
-                top = 0;
-            }
-            if (displayInfo != null && w.isDragResizing()) {
-                width = displayInfo.logicalWidth;
-                height = displayInfo.logicalHeight;
-            } else {
-                width = w.mCompatFrame.width();
-                height = w.mCompatFrame.height();
-            }
-        }
-
-        // Something is wrong and SurfaceFlinger will not like this,
-        // try to revert to sane values
-        if (width < 1) {
-            width = 1;
-        }
-        if (height < 1) {
-            height = 1;
-        }
-
-        // Adjust for surface insets.
-        final LayoutParams attrs = w.getAttrs();
-        final int displayId = w.getDisplayId();
-        float scale = 1.0f;
-        // Magnification is supported only for the default display.
-        if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) {
-            MagnificationSpec spec =
-                    mService.mAccessibilityController.getMagnificationSpecForWindowLocked(w);
-            if (spec != null && !spec.isNop()) {
-                scale = spec.scale;
-            }
-        }
-
-        width += scale * (attrs.surfaceInsets.left + attrs.surfaceInsets.right);
-        height += scale * (attrs.surfaceInsets.top + attrs.surfaceInsets.bottom);
-        left -= scale * attrs.surfaceInsets.left;
-        top -= scale * attrs.surfaceInsets.top;
-
-        mSurfaceController.setPositionInTransaction(left, top, recoveringMemory);
-        mSurfaceResized = mSurfaceController.setSizeInTransaction(width, height,
+        mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top, recoveringMemory);
+        mSurfaceResized = mSurfaceController.setSizeInTransaction(
+                mTmpSize.width(), mTmpSize.height(),
                 mDsDx * w.mHScale, mDtDx * w.mVScale,
                 mDsDy * w.mHScale, mDtDy * w.mVScale,
                 recoveringMemory);
 
         if (mSurfaceResized) {
+            mSurfaceResizeGeneration++;
             mAnimator.setPendingLayoutChanges(w.getDisplayId(),
                     WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
             w.applyDimLayerIfNeeded();
@@ -1532,7 +1501,7 @@
         applyAnimationLocked(transit, true);
         //TODO (multidisplay): Magnification is supported only for the default display.
         if (mService.mAccessibilityController != null
-                && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                && mWin.getDisplayId() == DEFAULT_DISPLAY) {
             mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit);
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 4b9a538..ae96658 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -542,11 +542,8 @@
 
         mService.scheduleAnimationLocked();
 
-        if (DEBUG_WINDOW_TRACE) {
-            Slog.e(TAG,
-                    "performSurfacePlacementInner exit: animating="
-                            + mService.mAnimator.mAnimating);
-        }
+        if (DEBUG_WINDOW_TRACE) Slog.e(TAG,
+                "performSurfacePlacementInner exit: animating=" + mService.mAnimator.isAnimating());
     }
 
     private void applySurfaceChangesTransaction(boolean recoveringMemory, int numDisplays,
@@ -932,6 +929,13 @@
                     win.prelayout();
                     mService.mPolicy.layoutWindowLw(win, null);
                     win.mLayoutSeq = seq;
+
+                    // Window frames may have changed. Update dim layer with the new bounds.
+                    final Task task = win.getTask();
+                    if (task != null) {
+                        displayContent.mDimLayerController.updateDimLayer(task);
+                    }
+
                     if (DEBUG_LAYOUT) Slog.v(TAG,
                             "  LAYOUT: mFrame="
                             + win.mFrame + " mContainingFrame="
@@ -986,7 +990,7 @@
             }
         }
 
-        // Window frames may have changed.  Tell the input dispatcher about it.
+        // Window frames may have changed. Tell the input dispatcher about it.
         mService.mInputMonitor.setUpdateInputWindowsNeededLw();
         if (updateInputWindows) {
             mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
@@ -1177,7 +1181,7 @@
                     ">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
             SurfaceControl.openTransaction();
             try {
-                mService.mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+                mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
             } finally {
                 SurfaceControl.closeTransaction();
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
@@ -1460,7 +1464,7 @@
                     appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
                 }
                 mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
-                mService.mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+                mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
             }
         }
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index eee7d92..c611503 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -156,11 +156,6 @@
 
 /**
  * Implementation of the device policy APIs.
- *
- * Locking policies:
- * - {@link DevicePolicyManagerService} must not call into {@link IActivityManager} within {@code
- * this} lock to avoid lock inversion.
- * - Methods that call into {@link IActivityManager} must have the "AM" suffix.
  */
 public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
 
@@ -1110,7 +1105,7 @@
                     .asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
         }
 
-        IActivityManager getIActivityManagerInner() {
+        IActivityManager getIActivityManager() {
             return ActivityManagerNative.getDefault();
         }
 
@@ -1241,16 +1236,6 @@
     }
 
     /**
-     * Caller must not hold {@code this} lock.  See also the class javadoc.
-     */
-    final IActivityManager getIActivityManager() {
-        if (Thread.holdsLock(this)) {
-            Slog.wtfStack(LOG_TAG, "Call to ActivityManager detected within DPMS lock");
-        }
-        return mInjector.getIActivityManagerInner();
-    }
-
-    /**
      * Instantiates the service.
      */
     public DevicePolicyManagerService(Context context) {
@@ -1364,6 +1349,8 @@
 
             // TODO PO may not have a class name either due to b/17652534.  Address that too.
 
+            updateDeviceOwnerLocked();
+
             // TODO Notify UM to update restrictions (?)
         }
     }
@@ -1625,25 +1612,17 @@
     @VisibleForTesting
     boolean isActiveAdminWithPolicyForUserLocked(ActiveAdmin admin, int reqPolicy,
             int userId) {
-        boolean ownsDevice = isDeviceOwner(admin.info.getComponent());
-        boolean ownsProfile = (getProfileOwner(userId) != null
-                && getProfileOwner(userId).getPackageName()
-                    .equals(admin.info.getPackageName()));
+        final boolean ownsDevice = isDeviceOwner(admin.info.getComponent(), userId);
+        final boolean ownsProfile = isProfileOwner(admin.info.getComponent(), userId);
 
         if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
-            if ((userId == UserHandle.USER_SYSTEM && ownsDevice) || (ownsDevice && ownsProfile)) {
-                return true;
-            }
+            return ownsDevice;
         } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
-            if ((userId == UserHandle.USER_SYSTEM && ownsDevice) || ownsProfile) {
-                return true;
-            }
+            // DO always has the PO power.
+            return ownsDevice || ownsProfile;
         } else {
-            if (admin.info.usesPolicy(reqPolicy)) {
-                return true;
-            }
+            return admin.info.usesPolicy(reqPolicy);
         }
-        return false;
     }
 
     void sendAdminCommandLocked(ActiveAdmin admin, String action) {
@@ -2050,23 +2029,36 @@
 
         validatePasswordOwnerLocked(policy);
         updateMaximumTimeToLockLocked(policy);
-        updateLockTaskPackages(policy.mLockTaskPackages, userHandle);
+        updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
         if (policy.mStatusBarDisabled) {
             setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle);
         }
     }
 
-    private void updateLockTaskPackages(List<String> packages, final int userId) {
-        final String[] copy = packages.toArray(new String[packages.size()]);
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    getIActivityManager().updateLockTaskPackages(userId, copy);
-                } catch (RemoteException willNotHappen) {
-                }
+    private void updateLockTaskPackagesLocked(List<String> packages, int userId) {
+        long ident = mInjector.binderClearCallingIdentity();
+        try {
+            mInjector.getIActivityManager()
+                    .updateLockTaskPackages(userId, packages.toArray(new String[packages.size()]));
+        } catch (RemoteException e) {
+            // Not gonna happen.
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
+    }
+
+    private void updateDeviceOwnerLocked() {
+        long ident = mInjector.binderClearCallingIdentity();
+        try {
+            if (getDeviceOwner() != null) {
+                mInjector.getIActivityManager()
+                        .updateDeviceOwner(getDeviceOwner().getPackageName());
             }
-        });
+        } catch (RemoteException e) {
+            // Not gonna happen.
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
     }
 
     static void validateQualityConstant(int quality) {
@@ -2137,21 +2129,14 @@
     }
 
     private void ensureDeviceOwnerUserStarted() {
-        if (!mOwners.hasDeviceOwner()) {
-            return;
-        }
-        final int userId = mOwners.getDeviceOwnerUserId();
-        if (userId == UserHandle.USER_SYSTEM) {
-            return;
-        }
-        if (VERBOSE_LOG) {
-            Log.v(LOG_TAG, "Starting non-system DO user: " + userId);
-        }
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
+        if (mOwners.hasDeviceOwner()) {
+            final int userId = mOwners.getDeviceOwnerUserId();
+            if (VERBOSE_LOG) {
+                Log.v(LOG_TAG, "Starting non-system DO user: " + userId);
+            }
+            if (userId != UserHandle.USER_SYSTEM) {
                 try {
-                    getIActivityManager().startUserInBackground(userId);
+                    mInjector.getIActivityManager().startUserInBackground(userId);
 
                     // STOPSHIP Prevent the DO user from being killed.
 
@@ -2159,7 +2144,7 @@
                     Slog.w(LOG_TAG, "Exception starting user", e);
                 }
             }
-        });
+        }
     }
 
     private void cleanUpOldUsers() {
@@ -2425,19 +2410,15 @@
         }
         enforceCrossUserPermission(userHandle);
         synchronized (this) {
-            return packageHasActiveAdminsLocked(packageName, userHandle);
-        }
-    }
-
-    boolean packageHasActiveAdminsLocked(String packageName, int userHandle) {
-        DevicePolicyData policy = getUserData(userHandle);
-        final int N = policy.mAdminList.size();
-        for (int i = 0; i < N; i++) {
-            if (policy.mAdminList.get(i).info.getPackageName().equals(packageName)) {
-                return true;
+            DevicePolicyData policy = getUserData(userHandle);
+            final int N = policy.mAdminList.size();
+            for (int i=0; i<N; i++) {
+                if (policy.mAdminList.get(i).info.getPackageName().equals(packageName)) {
+                    return true;
+                }
             }
+            return false;
         }
-        return false;
     }
 
     @Override
@@ -2452,8 +2433,9 @@
                 return;
             }
             if (admin.getUid() != mInjector.binderGetCallingUid()) {
-                // Active device owners must remain active admins.
-                if (isDeviceOwner(adminReceiver)) {
+                // Active device/profile owners must remain active admins.
+                if (isDeviceOwner(adminReceiver, userHandle)
+                        || isProfileOwner(adminReceiver, userHandle)) {
                     return;
                 }
                 mContext.enforceCallingOrSelfPermission(
@@ -3198,12 +3180,21 @@
     }
 
     @Override
-    public boolean resetPassword(String passwordOrNull, int flags) {
+    public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException {
         if (!mHasFeature) {
             return false;
         }
         final int userHandle = UserHandle.getCallingUserId();
-        enforceNotManagedProfile(userHandle, "reset the password");
+
+        long ident = mInjector.binderClearCallingIdentity();
+        try {
+            if (mUserManager.getCredentialOwnerProfile(userHandle) != userHandle) {
+                throw new SecurityException("You can not change password for this profile because"
+                    + " it shares the password with the owner profile");
+            }
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
 
         String password = passwordOrNull != null ? passwordOrNull : "";
 
@@ -3211,8 +3202,35 @@
         synchronized (this) {
             // This api can only be called by an active device admin,
             // so try to retrieve it to check that the caller is one.
-            getActiveAdminForCallerLocked(null,
+            final ActiveAdmin admin = getActiveAdminForCallerLocked(null,
                     DeviceAdminInfo.USES_POLICY_RESET_PASSWORD);
+            final ComponentName adminComponent = admin.info.getComponent();
+
+            // As of N, only profile owners and device owners can reset the password.
+            if (!(isProfileOwner(adminComponent, userHandle)
+                    || isDeviceOwner(adminComponent, userHandle))) {
+                final boolean preN = getTargetSdk(admin.info.getPackageName(), userHandle)
+                        < android.os.Build.VERSION_CODES.N;
+                // As of N, password resetting to empty/null is not allowed anymore.
+                // TODO Should we allow DO/PO to set an empty password?
+                if (TextUtils.isEmpty(password)) {
+                    if (!preN) {
+                        throw new SecurityException("Cannot call with null password");
+                    } else {
+                        Slog.e(LOG_TAG, "Cannot call with null password");
+                        return false;
+                    }
+                }
+                // As of N, password cannot be changed by the admin if it is already set.
+                if (isLockScreenSecureUnchecked(userHandle)) {
+                    if (!preN) {
+                        throw new SecurityException("Admin cannot change current password");
+                    } else {
+                        Slog.e(LOG_TAG, "Admin cannot change current password");
+                        return false;
+                    }
+                }
+            }
             quality = getPasswordQuality(null, userHandle);
             if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
                 int realQuality = LockPatternUtils.computePasswordQuality(password);
@@ -3314,9 +3332,9 @@
 
         // Don't do this with the lock held, because it is going to call
         // back in to the service.
-        long ident = mInjector.binderClearCallingIdentity();
+        ident = mInjector.binderClearCallingIdentity();
         try {
-            LockPatternUtils utils = new LockPatternUtils(mContext);
+            LockPatternUtils utils = mInjector.newLockPatternUtils();
             if (!TextUtils.isEmpty(password)) {
                 utils.saveLockPassword(password, null, quality, userHandle);
             } else {
@@ -3341,6 +3359,15 @@
         return true;
     }
 
+    private boolean isLockScreenSecureUnchecked(int userId) {
+        long ident = mInjector.binderClearCallingIdentity();
+        try {
+            return mInjector.newLockPatternUtils().isSecure(userId);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
+    }
+
     private void setDoNotAskCredentialsOnBoot() {
         synchronized (this) {
             DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
@@ -3696,10 +3723,11 @@
     }
 
     @Override
-    public void wipeData(int flags, final int userHandle) {
+    public void wipeData(int flags) {
         if (!mHasFeature) {
             return;
         }
+        final int userHandle = mInjector.userHandleGetCallingUserId();
         enforceCrossUserPermission(userHandle);
         synchronized (this) {
             // This API can only be called by an active device admin,
@@ -3712,8 +3740,7 @@
             long ident = mInjector.binderClearCallingIdentity();
             try {
                 if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
-                    if (userHandle != UserHandle.USER_SYSTEM
-                            || !isDeviceOwner(admin.info.getComponent())) {
+                    if (!isDeviceOwner(admin.info.getComponent(), userHandle)) {
                         throw new SecurityException(
                                "Only device owner admins can set WIPE_RESET_PROTECTION_DATA");
                     }
@@ -3740,7 +3767,7 @@
                 @Override
                 public void run() {
                     try {
-                        IActivityManager am = getIActivityManager();
+                        IActivityManager am = mInjector.getIActivityManager();
                         if (am.getCurrentUser().id == userHandle) {
                             am.switchUser(UserHandle.USER_SYSTEM);
                         }
@@ -4336,7 +4363,7 @@
             return;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
-        final int userHandle = UserHandle.getCallingUserId();
+        final int userHandle = mInjector.userHandleGetCallingUserId();
         synchronized (this) {
             ActiveAdmin ap = getActiveAdminForCallerLocked(who,
                     DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA);
@@ -4348,7 +4375,7 @@
         // Tell the user manager that the restrictions have changed.
         synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
             synchronized (this) {
-                if (isDeviceOwner(who)) {
+                if (isDeviceOwner(who, userHandle)) {
                     mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
                 } else {
                     mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
@@ -4496,6 +4523,7 @@
 
             mOwners.setDeviceOwner(admin, ownerName, userId);
             mOwners.writeDeviceOwner();
+            updateDeviceOwnerLocked();
             Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED);
 
             ident = mInjector.binderClearCallingIdentity();
@@ -4509,24 +4537,17 @@
         }
     }
 
-    public boolean isDeviceOwner(ComponentName who) {
-        if (!mHasFeature) {
-            return false;
-        }
+    public boolean isDeviceOwner(ComponentName who, int userId) {
         synchronized (this) {
-            return mOwners.hasDeviceOwner() && mOwners.getDeviceOwnerComponent().equals(who);
+            return mOwners.hasDeviceOwner()
+                    && mOwners.getDeviceOwnerUserId() == userId
+                    && mOwners.getDeviceOwnerComponent().equals(who);
         }
     }
 
-    @Override
-    public boolean isDeviceOwnerPackage(String packageName) {
-        if (!mHasFeature) {
-            return false;
-        }
-        synchronized (this) {
-            return mOwners.hasDeviceOwner()
-                    && mOwners.getDeviceOwnerComponent().getPackageName().equals(packageName);
-        }
+    public boolean isProfileOwner(ComponentName who, int userId) {
+        final ComponentName profileOwner = getProfileOwner(userId);
+        return who != null && who.equals(profileOwner);
     }
 
     @Override
@@ -4597,6 +4618,7 @@
 
             mOwners.clearDeviceOwner();
             mOwners.writeDeviceOwner();
+            updateDeviceOwnerLocked();
             // Reactivate backup service.
             long ident = mInjector.binderClearCallingIdentity();
             try {
@@ -4645,6 +4667,30 @@
         }
     }
 
+    @Override
+    public boolean setDeviceOwnerLockScreenInfo(ComponentName who, String info) {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        if (!mHasFeature) {
+            return false;
+        }
+
+        synchronized (this) {
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            long token = mInjector.binderClearCallingIdentity();
+            try {
+                new LockPatternUtils(mContext).setDeviceOwnerInfo(info);
+            } finally {
+                mInjector.binderRestoreCallingIdentity(token);
+            }
+            return true;
+        }
+    }
+
+    @Override
+    public String getDeviceOwnerLockScreenInfo() {
+        return new LockPatternUtils(mContext).getDeviceOwnerInfo();
+    }
+
     private void clearUserPoliciesLocked(UserHandle userHandle) {
         int userId = userHandle.getIdentifier();
         // Reset some of the user-specific policies
@@ -5357,14 +5403,14 @@
         }
     }
 
-    private boolean checkCallerIsCurrentUserOrProfileAM() {
+    private boolean checkCallerIsCurrentUserOrProfile() {
         int callingUserId = UserHandle.getCallingUserId();
         long token = mInjector.binderClearCallingIdentity();
         try {
             UserInfo currentUser;
             UserInfo callingUser = mUserManager.getUserInfo(callingUserId);
             try {
-                currentUser = getIActivityManager().getCurrentUser();
+                currentUser = mInjector.getIActivityManager().getCurrentUser();
             } catch (RemoteException e) {
                 Slog.e(LOG_TAG, "Failed to talk to activity managed.", e);
                 return false;
@@ -5395,7 +5441,7 @@
 
         // TODO When InputMethodManager supports per user calls remove
         //      this restriction.
-        if (!checkCallerIsCurrentUserOrProfileAM()) {
+        if (!checkCallerIsCurrentUserOrProfile()) {
             return false;
         }
 
@@ -5447,7 +5493,7 @@
     public List getPermittedInputMethodsForCurrentUser() {
         UserInfo currentUser;
         try {
-            currentUser = getIActivityManager().getCurrentUser();
+            currentUser = mInjector.getIActivityManager().getCurrentUser();
         } catch (RemoteException e) {
             Slog.e(LOG_TAG, "Failed to make remote calls to get current user", e);
             // Activity managed is dead, just allow all IMEs
@@ -5542,7 +5588,7 @@
                 }
 
                 // Start user in background.
-                getIActivityManager().startUserInBackground(userHandle);
+                mInjector.getIActivityManager().startUserInBackground(userHandle);
             } catch (RemoteException e) {
                 Slog.e(LOG_TAG, "Failed to make remote calls for configureUser", e);
             }
@@ -5575,20 +5621,20 @@
         Preconditions.checkNotNull(who, "ComponentName is null");
         synchronized (this) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-        }
 
-        long id = mInjector.binderClearCallingIdentity();
-        try {
-            int userId = UserHandle.USER_SYSTEM;
-            if (userHandle != null) {
-                userId = userHandle.getIdentifier();
+            long id = mInjector.binderClearCallingIdentity();
+            try {
+                int userId = UserHandle.USER_SYSTEM;
+                if (userHandle != null) {
+                    userId = userHandle.getIdentifier();
+                }
+                return mInjector.getIActivityManager().switchUser(userId);
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, "Couldn't switch user", e);
+                return false;
+            } finally {
+                mInjector.binderRestoreCallingIdentity(id);
             }
-            return getIActivityManager().switchUser(userId);
-        } catch (RemoteException e) {
-            Log.e(LOG_TAG, "Couldn't switch user", e);
-            return false;
-        } finally {
-            mInjector.binderRestoreCallingIdentity(id);
         }
     }
 
@@ -5622,9 +5668,8 @@
                 ActiveAdmin activeAdmin =
                         getActiveAdminForCallerLocked(who,
                                 DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-                final boolean isDeviceOwner = isDeviceOwner(who);
-                if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM
-                        && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
+                final boolean isDeviceOwner = isDeviceOwner(who, userHandle);
+                if (!isDeviceOwner && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
                     throw new SecurityException(
                             "Profile owners cannot set user restriction " + key);
                 }
@@ -6060,7 +6105,7 @@
 
         // Store the settings persistently.
         saveSettingsLocked(userHandle);
-        updateLockTaskPackages(packages, userHandle);
+        updateLockTaskPackagesLocked(packages, userHandle);
     }
 
     /**
@@ -6117,9 +6162,8 @@
             Bundle adminExtras = new Bundle();
             adminExtras.putString(DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE, pkg);
             for (ActiveAdmin admin : policy.mAdminList) {
-                boolean ownsDevice = isDeviceOwner(admin.info.getComponent());
-                boolean ownsProfile = (getProfileOwner(userHandle) != null
-                        && getProfileOwner(userHandle).equals(admin.info.getPackageName()));
+                final boolean ownsDevice = isDeviceOwner(admin.info.getComponent(), userHandle);
+                final boolean ownsProfile = isProfileOwner(admin.info.getComponent(), userHandle);
                 if (ownsDevice || ownsProfile) {
                     if (isEnabled) {
                         sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_LOCK_TASK_ENTERING,
@@ -6171,13 +6215,12 @@
     @Override
     public void setSecureSetting(ComponentName who, String setting, String value) {
         Preconditions.checkNotNull(who, "ComponentName is null");
-        int callingUserId = UserHandle.getCallingUserId();
-        final ContentResolver contentResolver = mContext.getContentResolver();
+        int callingUserId = mInjector.userHandleGetCallingUserId();
 
         synchronized (this) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
 
-            if (isDeviceOwner(who)) {
+            if (isDeviceOwner(who, mInjector.userHandleGetCallingUserId())) {
                 if (!SECURE_SETTINGS_DEVICEOWNER_WHITELIST.contains(setting)) {
                     throw new SecurityException(String.format(
                             "Permission denial: Device owners cannot update %1$s", setting));
@@ -6428,16 +6471,6 @@
             }
         }
 
-        @Override
-        public boolean isDeviceAdminPackage(int userId, String packageName) {
-            if (packageName == null) {
-                return false;
-            }
-            synchronized (DevicePolicyManagerService.this) {
-                return packageHasActiveAdminsLocked(packageName, userId);
-            }
-        }
-
         private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) {
             final List<OnCrossProfileWidgetProvidersChangeListener> listeners;
             synchronized (DevicePolicyManagerService.this) {
@@ -6499,13 +6532,26 @@
      * @param callerUid UID of the caller.
      * @return true if the caller is the device owner app
      */
-    private boolean isCallerDeviceOwner(int callerUid) {
-        String[] pkgs = mContext.getPackageManager().getPackagesForUid(callerUid);
-        for (String pkg : pkgs) {
-            if (isDeviceOwnerPackage(pkg)) {
-                return true;
+    @VisibleForTesting
+    boolean isCallerDeviceOwner(int callerUid) {
+        synchronized (this) {
+            if (!mOwners.hasDeviceOwner()) {
+                return false;
+            }
+            if (UserHandle.getUserId(callerUid) != mOwners.getDeviceOwnerUserId()) {
+                return false;
+            }
+            final String deviceOwnerPackageName = mOwners.getDeviceOwnerComponent()
+                    .getPackageName();
+            final String[] pkgs = mContext.getPackageManager().getPackagesForUid(callerUid);
+
+            for (String pkg : pkgs) {
+                if (deviceOwnerPackageName.equals(pkg)) {
+                    return true;
+                }
             }
         }
+
         return false;
     }
 
@@ -6585,10 +6631,8 @@
             getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
             long ident = mInjector.binderClearCallingIdentity();
             try {
-                final ApplicationInfo ai = mIPackageManager
-                        .getApplicationInfo(packageName, 0, user.getIdentifier());
-                final int targetSdkVersion = ai == null ? 0 : ai.targetSdkVersion;
-                if (targetSdkVersion < android.os.Build.VERSION_CODES.M) {
+                if (getTargetSdk(packageName, user.getIdentifier())
+                        < android.os.Build.VERSION_CODES.M) {
                     return false;
                 }
                 final PackageManager packageManager = mContext.getPackageManager();
@@ -6684,23 +6728,48 @@
             }
             return true;
         } else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE.equals(action)) {
-            if (getProfileOwner(callingUserId) != null) {
-                return false;
-            }
-            if (mInjector.settingsGlobalGetInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
-                return false;
-            }
-            if (callingUserId != UserHandle.USER_SYSTEM) {
-                // Device owner provisioning can only be initiated from system user.
-                return false;
-            }
-            return true;
+            return isDeviceOwnerProvisioningAllowed(callingUserId);
         } else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_USER.equals(action)) {
+            if (!UserManager.isSplitSystemUser()) {
+                // ACTION_PROVISION_MANAGED_USER only supported on split-user systems.
+                return false;
+            }
             if (hasUserSetupCompleted(callingUserId)) {
                 return false;
             }
             return true;
+        } else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE.equals(action)) {
+            if (!UserManager.isSplitSystemUser()) {
+                // ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE only supported on split-user systems.
+                return false;
+            }
+            return isDeviceOwnerProvisioningAllowed(callingUserId);
         }
         throw new IllegalArgumentException("Unknown provisioning action " + action);
     }
+
+    private boolean isDeviceOwnerProvisioningAllowed(int callingUserId) {
+        if (getProfileOwner(callingUserId) != null) {
+            return false;
+        }
+        if (mInjector.settingsGlobalGetInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
+            return false;
+        }
+        if (callingUserId != UserHandle.USER_SYSTEM) {
+            // Device owner provisioning can only be initiated from system user.
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns the target sdk version number that the given packageName was built for
+     * in the given user.
+     */
+    private int getTargetSdk(String packageName, int userId) throws RemoteException {
+        final ApplicationInfo ai = mIPackageManager
+                .getApplicationInfo(packageName, 0, userId);
+        final int targetSdkVersion = ai == null ? 0 : ai.targetSdkVersion;
+        return targetSdkVersion;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 6419338..2c01b8a 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -128,7 +128,7 @@
         }
 
         @Override
-        IActivityManager getIActivityManagerInner() {
+        IActivityManager getIActivityManager() {
             return context.iactivityManager;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ca3d950..36980e3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -476,6 +476,10 @@
         // Fire!
         assertTrue(dpm.setDeviceOwner(admin1, "owner-name"));
 
+        // Verify internal calls.
+        verify(mContext.iactivityManager, times(1)).updateDeviceOwner(
+                eq(admin1.getPackageName()));
+
         // TODO We should check if the caller has called clearCallerIdentity().
         verify(mContext.ibackupManager, times(1)).setBackupServiceActive(
                 eq(UserHandle.USER_SYSTEM), eq(false));
@@ -542,6 +546,10 @@
         dpm.setActiveAdmin(admin1, /* replace =*/ false);
         assertTrue(dpm.setDeviceOwner(admin1, "owner-name"));
 
+        // Verify internal calls.
+        verify(mContext.iactivityManager, times(1)).updateDeviceOwner(
+                eq(admin1.getPackageName()));
+
         assertEquals(admin1.getPackageName(), dpm.getDeviceOwner());
 
         // Set up other mocks.
@@ -576,6 +584,10 @@
         dpm.setActiveAdmin(admin1, /* replace =*/ false);
         assertTrue(dpm.setDeviceOwner(admin1, "owner-name"));
 
+        // Verify internal calls.
+        verify(mContext.iactivityManager, times(1)).updateDeviceOwner(
+                eq(admin1.getPackageName()));
+
         assertEquals(admin1.getPackageName(), dpm.getDeviceOwner());
 
         // Now call clear from the secondary user, which should throw.
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index f078a35..2b0919b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -215,11 +215,11 @@
             mPowerManager = getContext().getSystemService(PowerManager.class);
 
             mScreenOnSystemTimeSnapshot = System.currentTimeMillis();
-            synchronized (this) {
+            synchronized (mLock) {
                 mScreenOnTime = readScreenOnTimeLocked();
             }
             mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
-            synchronized (this) {
+            synchronized (mLock) {
                 updateDisplayLocked();
             }
         } else if (phase == PHASE_BOOT_COMPLETED) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index a96c164..f396c2d 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -291,21 +291,26 @@
                 String curService = Settings.Secure.getStringForUser(
                         mResolver, Settings.Secure.VOICE_INTERACTION_SERVICE, mCurUser);
                 ComponentName serviceComponent = null;
+                ServiceInfo serviceInfo = null;
                 if (curService != null && !curService.isEmpty()) {
                     try {
                         serviceComponent = ComponentName.unflattenFromString(curService);
-                    } catch (RuntimeException e) {
+                        serviceInfo = AppGlobals.getPackageManager()
+                                .getServiceInfo(serviceComponent, 0, mCurUser);
+                    } catch (RuntimeException | RemoteException e) {
                         Slog.wtf(TAG, "Bad voice interaction service name " + curService, e);
                         serviceComponent = null;
+                        serviceInfo = null;
                     }
                 }
+
                 if (force || mImpl == null || mImpl.mUser != mCurUser
                         || !mImpl.mComponent.equals(serviceComponent)) {
                     mSoundTriggerHelper.stopAllRecognitions();
                     if (mImpl != null) {
                         mImpl.shutdownLocked();
                     }
-                    if (serviceComponent != null) {
+                    if (serviceComponent != null && serviceInfo != null) {
                         mImpl = new VoiceInteractionManagerServiceImpl(mContext,
                                 UiThread.getHandler(), this, mCurUser, serviceComponent);
                         mImpl.startLocked();
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index deafe20..73d8585 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -190,22 +190,32 @@
 
 bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
                                 std::unique_ptr<Value> value, IDiagnostics* diag) {
-    return addResourceImpl(name, {}, config, std::move(value), kValidNameChars, diag);
+    return addResourceImpl(name, {}, config, std::move(value), kValidNameChars,
+                           resolveValueCollision, diag);
 }
 
 bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
                                 const ConfigDescription& config, std::unique_ptr<Value> value,
                                 IDiagnostics* diag) {
-    return addResourceImpl(name, resId, config, std::move(value), kValidNameChars, diag);
+    return addResourceImpl(name, resId, config, std::move(value), kValidNameChars,
+                           resolveValueCollision, diag);
 }
 
 bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
                                      const Source& source, const StringPiece16& path,
                                      IDiagnostics* diag) {
+    return addFileReference(name, config, source, path, resolveValueCollision, diag);
+}
+
+bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
+                                     const Source& source, const StringPiece16& path,
+                                     std::function<int(Value*,Value*)> conflictResolver,
+                                     IDiagnostics* diag) {
     std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
             stringPool.makeRef(path));
     fileRef->setSource(source);
-    return addResourceImpl(name, ResourceId{}, config, std::move(fileRef), kValidNameChars, diag);
+    return addResourceImpl(name, ResourceId{}, config, std::move(fileRef), kValidNameChars,
+                           conflictResolver, diag);
 }
 
 bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
@@ -213,7 +223,7 @@
                                             std::unique_ptr<Value> value,
                                             IDiagnostics* diag) {
     return addResourceImpl(name, ResourceId{}, config, std::move(value), kValidNameMangledChars,
-                           diag);
+                           resolveValueCollision, diag);
 }
 
 bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
@@ -221,12 +231,17 @@
                                             const ConfigDescription& config,
                                             std::unique_ptr<Value> value,
                                             IDiagnostics* diag) {
-    return addResourceImpl(name, id, config, std::move(value), kValidNameMangledChars, diag);
+    return addResourceImpl(name, id, config, std::move(value), kValidNameMangledChars,
+                           resolveValueCollision, diag);
 }
 
-bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
-                                    const ConfigDescription& config, std::unique_ptr<Value> value,
-                                    const char16_t* validChars, IDiagnostics* diag) {
+bool ResourceTable::addResourceImpl(const ResourceNameRef& name,
+                                    const ResourceId resId,
+                                    const ConfigDescription& config,
+                                    std::unique_ptr<Value> value,
+                                    const char16_t* validChars,
+                                    std::function<int(Value*,Value*)> conflictResolver,
+                                    IDiagnostics* diag) {
     assert(value && "value can't be nullptr");
     assert(diag && "diagnostics can't be nullptr");
 
@@ -289,7 +304,7 @@
         // This resource did not exist before, add it.
         entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) });
     } else {
-        int collisionResult = resolveValueCollision(iter->value.get(), value.get());
+        int collisionResult = conflictResolver(iter->value.get(), value.get());
         if (collisionResult > 0) {
             // Take the incoming value.
             iter->value = std::move(value);
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 980504b..6b7b07e 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -163,7 +163,12 @@
                      IDiagnostics* diag);
 
     bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
-                          const Source& source, const StringPiece16& path, IDiagnostics* diag);
+                          const Source& source, const StringPiece16& path,
+                          IDiagnostics* diag);
+
+    bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
+                          const Source& source, const StringPiece16& path,
+                          std::function<int(Value*,Value*)> conflictResolver, IDiagnostics* diag);
 
     /**
      * Same as addResource, but doesn't verify the validity of the name. This is used
@@ -221,9 +226,14 @@
 private:
     ResourceTablePackage* findOrCreatePackage(const StringPiece16& name);
 
-    bool addResourceImpl(const ResourceNameRef& name, ResourceId resId,
-                         const ConfigDescription& config, std::unique_ptr<Value> value,
-                         const char16_t* validChars, IDiagnostics* diag);
+    bool addResourceImpl(const ResourceNameRef& name,
+                         ResourceId resId,
+                         const ConfigDescription& config,
+                         std::unique_ptr<Value> value,
+                         const char16_t* validChars,
+                         std::function<int(Value*,Value*)> conflictResolver,
+                         IDiagnostics* diag);
+
     bool setSymbolStateImpl(const ResourceNameRef& name, ResourceId resId,
                             const Symbol& symbol, const char16_t* validChars, IDiagnostics* diag);
 };
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 9ce3734..93f2dc6f 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -48,6 +48,7 @@
     std::string outputPath;
     std::string manifestPath;
     std::vector<std::string> includePaths;
+    std::vector<std::string> overlayFiles;
     Maybe<std::string> generateJavaClassPath;
     std::vector<std::string> extraJavaPackages;
     Maybe<std::string> generateProguardRulesPath;
@@ -88,9 +89,11 @@
     }
 };
 
-struct LinkCommand {
-    LinkOptions mOptions;
-    LinkContext mContext;
+class LinkCommand {
+public:
+    LinkCommand(const LinkOptions& options) :
+            mOptions(options), mContext(), mFinalTable() {
+    }
 
     std::string buildResourceFileName(const ResourceFile& resFile) {
         std::stringstream out;
@@ -117,8 +120,7 @@
         AssetManagerSymbolTableBuilder builder;
         for (const std::string& path : mOptions.includePaths) {
             if (mOptions.verbose) {
-                mContext.getDiagnostics()->note(
-                        DiagMessage(Source{ path }) << "loading include path");
+                mContext.getDiagnostics()->note(DiagMessage(path) << "loading include path");
             }
 
             std::unique_ptr<android::AssetManager> assetManager =
@@ -126,7 +128,7 @@
             int32_t cookie = 0;
             if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) {
                 mContext.getDiagnostics()->error(
-                        DiagMessage(Source{ path }) << "failed to load include path");
+                        DiagMessage(path) << "failed to load include path");
                 return {};
             }
             builder.add(std::move(assetManager));
@@ -141,12 +143,12 @@
         std::string errorStr;
         Maybe<android::FileMap> map = file::mmapPath(input, &errorStr);
         if (!map) {
-            mContext.getDiagnostics()->error(DiagMessage(Source{ input }) << errorStr);
+            mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
             return {};
         }
 
         std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
-        BinaryResourceParser parser(&mContext, table.get(), Source{ input },
+        BinaryResourceParser parser(&mContext, table.get(), Source(input),
                                     map.value().getDataPtr(), map.value().getDataLength());
         if (!parser.parse()) {
             return {};
@@ -160,11 +162,11 @@
     std::unique_ptr<XmlResource> loadXml(const std::string& path) {
         std::ifstream fin(path, std::ifstream::binary);
         if (!fin) {
-            mContext.getDiagnostics()->error(DiagMessage(Source{ path }) << strerror(errno));
+            mContext.getDiagnostics()->error(DiagMessage(path) << strerror(errno));
             return {};
         }
 
-        return xml::inflate(&fin, mContext.getDiagnostics(), Source{ path });
+        return xml::inflate(&fin, mContext.getDiagnostics(), Source(path));
     }
 
     /**
@@ -255,9 +257,9 @@
         return {};
     }
 
-    bool verifyNoExternalPackages(ResourceTable* table) {
+    bool verifyNoExternalPackages() {
         bool error = false;
-        for (const auto& package : table->packages) {
+        for (const auto& package : mFinalTable.packages) {
             if (mContext.getCompilationPackage() != package->name ||
                     !package->id || package->id.value() != mContext.getPackageId()) {
                 // We have a package that is not related to the one we're building!
@@ -401,6 +403,103 @@
         return true;
     }
 
+    bool mergeStaticLibrary(const std::string& input) {
+        // TODO(adamlesinski): Load resources from a static library APK and merge the table into
+        // TableMerger.
+        mContext.getDiagnostics()->warn(DiagMessage()
+                                        << "linking static libraries not supported yet: "
+                                        << input);
+        return true;
+    }
+
+    bool mergeResourceTable(const std::string& input, bool override) {
+        if (mOptions.verbose) {
+            mContext.getDiagnostics()->note(DiagMessage() << "linking " << input);
+        }
+
+        std::unique_ptr<ResourceTable> table = loadTable(input);
+        if (!table) {
+            return false;
+        }
+
+        if (!mTableMerger->merge(Source(input), table.get(), override)) {
+            return false;
+        }
+        return true;
+    }
+
+    bool mergeCompiledFile(const std::string& input, ResourceFile&& file, bool override) {
+        if (file.name.package.empty()) {
+            file.name.package = mContext.getCompilationPackage().toString();
+        }
+
+        ResourceNameRef resName = file.name;
+
+        Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(file.name);
+        if (mangledName) {
+            resName = mangledName.value();
+        }
+
+        std::function<int(Value*,Value*)> resolver;
+        if (override) {
+            resolver = [](Value* a, Value* b) -> int {
+                int result = ResourceTable::resolveValueCollision(a, b);
+                if (result == 0) {
+                    // Always accept the new value if it would normally conflict (override).
+                    result = 1;
+                }
+                return result;
+            };
+        } else {
+            // Otherwise use the default resolution.
+            resolver = ResourceTable::resolveValueCollision;
+        }
+
+        // Add this file to the table.
+        if (!mFinalTable.addFileReference(resName, file.config, file.source,
+                                          util::utf8ToUtf16(buildResourceFileName(file)),
+                                          resolver, mContext.getDiagnostics())) {
+            return false;
+        }
+
+        // Add the exports of this file to the table.
+        for (SourcedResourceName& exportedSymbol : file.exportedSymbols) {
+            if (exportedSymbol.name.package.empty()) {
+                exportedSymbol.name.package = mContext.getCompilationPackage().toString();
+            }
+
+            ResourceNameRef resName = exportedSymbol.name;
+
+            Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
+                    exportedSymbol.name);
+            if (mangledName) {
+                resName = mangledName.value();
+            }
+
+            std::unique_ptr<Id> id = util::make_unique<Id>();
+            id->setSource(file.source.withLine(exportedSymbol.line));
+            bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id),
+                    mContext.getDiagnostics());
+            if (!result) {
+                return false;
+            }
+        }
+
+        mFilesToProcess[resName.toResourceName()] = FileToProcess{ Source(input), std::move(file) };
+        return true;
+    }
+
+    bool processFile(const std::string& input, bool override) {
+        if (util::stringEndsWith<char>(input, ".apk")) {
+            return mergeStaticLibrary(input);
+        } else if (util::stringEndsWith<char>(input, ".arsc.flat")) {
+            return mergeResourceTable(input, override);
+        } else if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) {
+            return mergeCompiledFile(input, std::move(maybeF.value()), override);
+        }
+        return false;
+    }
+
     int run(const std::vector<std::string>& inputFiles) {
         // Load the AndroidManifest.xml
         std::unique_ptr<XmlResource> manifestXml = loadXml(mOptions.manifestPath);
@@ -438,82 +537,25 @@
             return 1;
         }
 
+        mTableMerger = util::make_unique<TableMerger>(&mContext, &mFinalTable);
+
         if (mOptions.verbose) {
             mContext.getDiagnostics()->note(
                     DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' "
                                   << "with package ID " << std::hex << (int) mContext.mPackageId);
         }
 
-        ResourceTable mergedTable;
-        TableMerger tableMerger(&mContext, &mergedTable);
-
-        struct FilesToProcess {
-            Source source;
-            ResourceFile file;
-        };
-
         bool error = false;
-        std::queue<FilesToProcess> filesToProcess;
+
         for (const std::string& input : inputFiles) {
-            if (util::stringEndsWith<char>(input, ".apk")) {
-                // TODO(adamlesinski): Load resources from a static library APK
-                //                     Merge the table into TableMerger.
+            if (!processFile(input, false)) {
+                error = true;
+            }
+        }
 
-            } else if (util::stringEndsWith<char>(input, ".arsc.flat")) {
-                if (mOptions.verbose) {
-                    mContext.getDiagnostics()->note(DiagMessage() << "linking " << input);
-                }
-
-                std::unique_ptr<ResourceTable> table = loadTable(input);
-                if (!table) {
-                    return 1;
-                }
-
-                if (!tableMerger.merge(Source(input), table.get())) {
-                    return 1;
-                }
-
-            } else {
-                // Extract the exported IDs here so we can build the resource table.
-                if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) {
-                    ResourceFile& f = maybeF.value();
-
-                    if (f.name.package.empty()) {
-                        f.name.package = mContext.getCompilationPackage().toString();
-                    }
-
-                    Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(f.name);
-
-                    // Add this file to the table.
-                    if (!mergedTable.addFileReference(mangledName ? mangledName.value() : f.name,
-                                                      f.config, f.source,
-                                                      util::utf8ToUtf16(buildResourceFileName(f)),
-                                                      mContext.getDiagnostics())) {
-                        error = true;
-                    }
-
-                    // Add the exports of this file to the table.
-                    for (SourcedResourceName& exportedSymbol : f.exportedSymbols) {
-                        if (exportedSymbol.name.package.empty()) {
-                            exportedSymbol.name.package = mContext.getCompilationPackage()
-                                    .toString();
-                        }
-
-                        Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
-                                exportedSymbol.name);
-                        std::unique_ptr<Id> id = util::make_unique<Id>();
-                        id->setSource(f.source.withLine(exportedSymbol.line));
-                        if (!mergedTable.addResourceAllowMangled(
-                                mangledName ? mangledName.value() : exportedSymbol.name,
-                                {}, std::move(id), mContext.getDiagnostics())) {
-                            error = true;
-                        }
-                    }
-
-                    filesToProcess.push(FilesToProcess{ Source(input), std::move(f) });
-                } else {
-                    return 1;
-                }
+        for (const std::string& input : mOptions.overlayFiles) {
+            if (!processFile(input, true)) {
+                error = true;
             }
         }
 
@@ -522,13 +564,13 @@
             return 1;
         }
 
-        if (!verifyNoExternalPackages(&mergedTable)) {
+        if (!verifyNoExternalPackages()) {
             return 1;
         }
 
         if (!mOptions.staticLib) {
             PrivateAttributeMover mover;
-            if (!mover.consume(&mContext, &mergedTable)) {
+            if (!mover.consume(&mContext, &mFinalTable)) {
                 mContext.getDiagnostics()->error(
                         DiagMessage() << "failed moving private attributes");
                 return 1;
@@ -537,22 +579,22 @@
 
         {
             IdAssigner idAssigner;
-            if (!idAssigner.consume(&mContext, &mergedTable)) {
+            if (!idAssigner.consume(&mContext, &mFinalTable)) {
                 mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
                 return 1;
             }
         }
 
-        mContext.mNameMangler = util::make_unique<NameMangler>(
-                NameManglerPolicy{ mContext.mCompilationPackage, tableMerger.getMergedPackages() });
+        mContext.mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{
+                mContext.mCompilationPackage, mTableMerger->getMergedPackages() });
         mContext.mSymbols = JoinedSymbolTableBuilder()
-                .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mergedTable))
+                .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mFinalTable))
                 .addSymbolTable(std::move(mContext.mSymbols))
                 .build();
 
         {
             ReferenceLinker linker;
-            if (!linker.consume(&mContext, &mergedTable)) {
+            if (!linker.consume(&mContext, &mFinalTable)) {
                 mContext.getDiagnostics()->error(DiagMessage() << "failed linking references");
                 return 1;
             }
@@ -598,20 +640,20 @@
             }
         }
 
-        for (; !filesToProcess.empty(); filesToProcess.pop()) {
-            FilesToProcess& f = filesToProcess.front();
-            if (f.file.name.type != ResourceType::kRaw &&
-                    util::stringEndsWith<char>(f.source.path, ".xml.flat")) {
+        for (auto& pair : mFilesToProcess) {
+            FileToProcess& file = pair.second;
+            if (file.file.name.type != ResourceType::kRaw &&
+                    util::stringEndsWith<char>(file.source.path, ".xml.flat")) {
                 if (mOptions.verbose) {
-                    mContext.getDiagnostics()->note(DiagMessage() << "linking " << f.source.path);
+                    mContext.getDiagnostics()->note(DiagMessage() << "linking " << file.source.path);
                 }
 
-                std::unique_ptr<XmlResource> xmlRes = loadBinaryXmlSkipFileExport(f.source.path);
+                std::unique_ptr<XmlResource> xmlRes = loadBinaryXmlSkipFileExport(file.source.path);
                 if (!xmlRes) {
                     return 1;
                 }
 
-                xmlRes->file = std::move(f.file);
+                xmlRes->file = std::move(file.file);
 
                 XmlReferenceLinker xmlLinker;
                 if (xmlLinker.consume(&mContext, xmlRes.get())) {
@@ -631,7 +673,7 @@
                     }
 
                     if (!mOptions.noAutoVersion) {
-                        Maybe<ResourceTable::SearchResult> result = mergedTable.findResource(
+                        Maybe<ResourceTable::SearchResult> result = mFinalTable.findResource(
                                 xmlRes->file.name);
                         for (int sdkLevel : xmlLinker.getSdkLevels()) {
                             if (sdkLevel > xmlRes->file.config.sdkVersion &&
@@ -639,7 +681,7 @@
                                                                     xmlRes->file.config,
                                                                     sdkLevel)) {
                                 xmlRes->file.config.sdkVersion = sdkLevel;
-                                if (!mergedTable.addFileReference(xmlRes->file.name,
+                                if (!mFinalTable.addFileReference(xmlRes->file.name,
                                                                   xmlRes->file.config,
                                                                   xmlRes->file.source,
                                                                   util::utf8ToUtf16(
@@ -662,10 +704,11 @@
                 }
             } else {
                 if (mOptions.verbose) {
-                    mContext.getDiagnostics()->note(DiagMessage() << "copying " << f.source.path);
+                    mContext.getDiagnostics()->note(DiagMessage() << "copying "
+                                                    << file.source.path);
                 }
 
-                if (!copyFileToArchive(f.source.path, buildResourceFileName(f.file), 0,
+                if (!copyFileToArchive(file.source.path, buildResourceFileName(file.file), 0,
                                        archiveWriter.get())) {
                     error = true;
                 }
@@ -679,13 +722,13 @@
 
         if (!mOptions.noAutoVersion) {
             AutoVersioner versioner;
-            if (!versioner.consume(&mContext, &mergedTable)) {
+            if (!versioner.consume(&mContext, &mFinalTable)) {
                 mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles");
                 return 1;
             }
         }
 
-        if (!flattenTable(&mergedTable, archiveWriter.get())) {
+        if (!flattenTable(&mFinalTable, archiveWriter.get())) {
             mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc");
             return 1;
         }
@@ -704,7 +747,7 @@
                 // to the original package, and private and public symbols to the private package.
 
                 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
-                if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage(),
+                if (!writeJavaFile(&mFinalTable, mContext.getCompilationPackage(),
                                    mContext.getCompilationPackage(), options)) {
                     return 1;
                 }
@@ -713,12 +756,12 @@
                 outputPackage = mOptions.privateSymbols.value();
             }
 
-            if (!writeJavaFile(&mergedTable, actualPackage, outputPackage, options)) {
+            if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
                 return 1;
             }
 
             for (std::string& extraPackage : mOptions.extraJavaPackages) {
-                if (!writeJavaFile(&mergedTable, actualPackage, util::utf8ToUtf16(extraPackage),
+                if (!writeJavaFile(&mFinalTable, actualPackage, util::utf8ToUtf16(extraPackage),
                                    options)) {
                     return 1;
                 }
@@ -732,10 +775,10 @@
         }
 
         if (mOptions.verbose) {
-            Debug::printTable(&mergedTable);
-            for (; !tableMerger.getFileMergeQueue()->empty();
-                    tableMerger.getFileMergeQueue()->pop()) {
-                const FileToMerge& f = tableMerger.getFileMergeQueue()->front();
+            Debug::printTable(&mFinalTable);
+            for (; !mTableMerger->getFileMergeQueue()->empty();
+                    mTableMerger->getFileMergeQueue()->pop()) {
+                const FileToMerge& f = mTableMerger->getFileMergeQueue()->front();
                 mContext.getDiagnostics()->note(
                         DiagMessage() << f.srcPath << " -> " << f.dstPath << " from (0x"
                                       << std::hex << (uintptr_t) f.srcTable << std::dec);
@@ -744,6 +787,18 @@
 
         return 0;
     }
+
+private:
+    LinkOptions mOptions;
+    LinkContext mContext;
+    ResourceTable mFinalTable;
+    std::unique_ptr<TableMerger> mTableMerger;
+
+    struct FileToProcess {
+        Source source;
+        ResourceFile file;
+    };
+    std::map<ResourceName, FileToProcess> mFilesToProcess;
 };
 
 int link(const std::vector<StringPiece>& args) {
@@ -755,6 +810,9 @@
             .requiredFlag("--manifest", "Path to the Android manifest to build",
                           &options.manifestPath)
             .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
+            .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics. "
+                              "The last conflicting resource given takes precedence.",
+                              &options.overlayFiles)
             .optionalFlag("--java", "Directory in which to generate R.java",
                           &options.generateJavaClassPath)
             .optionalFlag("--proguard", "Output file for generated Proguard rules",
@@ -794,7 +852,7 @@
         options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value());
     }
 
-    LinkCommand cmd = { options };
+    LinkCommand cmd(options);
     return cmd.run(flags.getArgs());
 }
 
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 1eea410..a06a1bf 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -37,7 +37,7 @@
 /**
  * This will merge packages with the same package name (or no package name).
  */
-bool TableMerger::merge(const Source& src, ResourceTable* table) {
+bool TableMerger::merge(const Source& src, ResourceTable* table, bool overrideExisting) {
     const uint8_t desiredPackageId = mContext->getPackageId();
 
     bool error = false;
@@ -55,7 +55,7 @@
             // mangled, then looked up at resolution time.
             // Also, when linking, we convert references with no package name to use
             // the compilation package name.
-            if (!doMerge(src, table, package.get(), false)) {
+            if (!doMerge(src, table, package.get(), false, overrideExisting)) {
                 error = true;
             }
         }
@@ -79,7 +79,7 @@
 
         bool mangle = packageName != mContext->getCompilationPackage();
         mMergedPackages.insert(package->name);
-        if (!doMerge(src, table, package.get(), mangle)) {
+        if (!doMerge(src, table, package.get(), mangle, false)) {
             error = true;
         }
     }
@@ -87,7 +87,8 @@
 }
 
 bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable,
-                          ResourceTablePackage* srcPackage, const bool manglePackage) {
+                          ResourceTablePackage* srcPackage, const bool manglePackage,
+                          const bool overrideExisting) {
     bool error = false;
 
     for (auto& srcType : srcPackage->types) {
@@ -149,7 +150,7 @@
                 if (iter != dstEntry->values.end() && iter->config == srcValue.config) {
                     const int collisionResult = ResourceTable::resolveValueCollision(
                             iter->value.get(), srcValue.value.get());
-                    if (collisionResult == 0) {
+                    if (collisionResult == 0 && !overrideExisting) {
                         // Error!
                         ResourceNameRef resourceName(srcPackage->name,
                                                      srcType->type,
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index c903f1b..a2c9dbf 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -63,7 +63,7 @@
     /**
      * Merges resources from the same or empty package. This is for local sources.
      */
-    bool merge(const Source& src, ResourceTable* table);
+    bool merge(const Source& src, ResourceTable* table, bool overrideExisting);
 
     /**
      * Merges resources from the given package, mangling the name. This is for static libraries.
@@ -79,7 +79,7 @@
     std::queue<FileToMerge> mFilesToMerge;
 
     bool doMerge(const Source& src, ResourceTable* srcTable, ResourceTablePackage* srcPackage,
-                 const bool manglePackage);
+                 const bool manglePackage, const bool overrideExisting);
 
     std::unique_ptr<Value> cloneAndMangle(ResourceTable* table, const std::u16string& package,
                                           Value* value);
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 0af4314..b7ffba7 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -59,7 +59,7 @@
     ResourceTable finalTable;
     TableMerger merger(mContext.get(), &finalTable);
 
-    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_TRUE(merger.merge({}, tableA.get(), false));
     ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get()));
 
     EXPECT_TRUE(merger.getMergedPackages().count(u"com.app.b") != 0);
@@ -89,7 +89,7 @@
     ResourceTable finalTable;
     TableMerger merger(mContext.get(), &finalTable);
 
-    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_TRUE(merger.merge({}, tableA.get(), false));
     ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get()));
 
     FileReference* f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/file");
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 5db1bde..723e827 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -23,6 +23,7 @@
 import com.android.ide.common.rendering.api.ResourceValue;
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.BridgeConstants;
+import com.android.layoutlib.bridge.MockView;
 import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
 import com.android.layoutlib.bridge.android.support.DrawerLayoutUtil;
@@ -126,6 +127,9 @@
             if (view == null) {
                 view = loadCustomView(name, attrs);
             }
+        } catch (InflateException e) {
+            // Don't catch the InflateException below as that results in hiding the real cause.
+            throw e;
         } catch (Exception e) {
             // Wrap the real exception in a ClassNotFoundException, so that the calling method
             // can deal with it.
@@ -154,23 +158,30 @@
                 }
                 ta.recycle();
             }
-            final Object lastContext = mConstructorArgs[0];
-            mConstructorArgs[0] = context;
-            // try to load the class from using the custom view loader
-            try {
-                view = loadCustomView(name, attrs);
-            } catch (Exception e2) {
-                // Wrap the real exception in an InflateException so that the calling
-                // method can deal with it.
-                InflateException exception = new InflateException();
-                if (!e2.getClass().equals(ClassNotFoundException.class)) {
-                    exception.initCause(e2);
-                } else {
-                    exception.initCause(e);
+            if (!(e.getCause() instanceof ClassNotFoundException)) {
+                // There is some unknown inflation exception in inflating a View that was found.
+                view = new MockView(context, attrs);
+                ((MockView) view).setText(name);
+                Bridge.getLog().error(LayoutLog.TAG_BROKEN, e.getMessage(), e, null);
+            } else {
+                final Object lastContext = mConstructorArgs[0];
+                mConstructorArgs[0] = context;
+                // try to load the class from using the custom view loader
+                try {
+                    view = loadCustomView(name, attrs);
+                } catch (Exception e2) {
+                    // Wrap the real exception in an InflateException so that the calling
+                    // method can deal with it.
+                    InflateException exception = new InflateException();
+                    if (!e2.getClass().equals(ClassNotFoundException.class)) {
+                        exception.initCause(e2);
+                    } else {
+                        exception.initCause(e);
+                    }
+                    throw exception;
+                } finally {
+                    mConstructorArgs[0] = lastContext;
                 }
-                throw exception;
-            } finally {
-                mConstructorArgs[0] = lastContext;
             }
         }
 
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
index 44a9aad..d392f21 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
@@ -17,39 +17,90 @@
 package com.android.layoutlib.bridge;
 
 import android.content.Context;
-import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
 /**
  * Base class for mocked views.
- *
- * TODO: implement onDraw and draw a rectangle in a random color with the name of the class
- * (or better the id of the view).
+ * <p/>
+ * FrameLayout with a single TextView. Doesn't allow adding any other views to itself.
  */
-public class MockView extends TextView {
+public class MockView extends FrameLayout {
+
+    private final TextView mView;
+
+    public MockView(Context context) {
+        this(context, null);
+    }
 
     public MockView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public MockView(Context context, AttributeSet attrs, int defStyle) {
-        this(context, attrs, defStyle, 0);
+    public MockView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
     }
 
     public MockView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-
-        setText(this.getClass().getSimpleName());
-        setTextColor(0xFF000000);
+        mView = new TextView(context, attrs);
+        mView.setTextColor(0xFF000000);
         setGravity(Gravity.CENTER);
+        setText(getClass().getSimpleName());
+        addView(mView);
+        setBackgroundColor(0xFF7F7F7F);
+    }
+
+    // Only allow adding one TextView.
+    @Override
+    public void addView(View child) {
+        if (child == mView) {
+            super.addView(child);
+        }
     }
 
     @Override
-    public void onDraw(Canvas canvas) {
-        canvas.drawARGB(0xFF, 0x7F, 0x7F, 0x7F);
+    public void addView(View child, int index) {
+        if (child == mView) {
+            super.addView(child, index);
+        }
+    }
 
-        super.onDraw(canvas);
+    @Override
+    public void addView(View child, int width, int height) {
+        if (child == mView) {
+            super.addView(child, width, height);
+        }
+    }
+
+    @Override
+    public void addView(View child, ViewGroup.LayoutParams params) {
+        if (child == mView) {
+            super.addView(child, params);
+        }
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        if (child == mView) {
+            super.addView(child, index, params);
+        }
+    }
+
+    // The following methods are called by the IDE via reflection, and should be considered part
+    // of the API.
+    // Historically, MockView used to be a textView and had these methods. Now, we simply delegate
+    // them to the contained textView.
+
+    public void setText(CharSequence text) {
+        mView.setText(text);
+    }
+
+    public void setGravity(int gravity) {
+        mView.setGravity(gravity);
     }
 }