Merge "Extends MtpDatabase so that it can handle child documents."
diff --git a/api/current.txt b/api/current.txt
index 3b4e3a9..62f0113 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -34268,6 +34268,18 @@
   public deprecated class FloatMath {
   }
 
+  public abstract class FloatProperty extends android.util.Property {
+    ctor public FloatProperty(java.lang.String);
+    method public final void set(T, java.lang.Float);
+    method public abstract void setValue(T, float);
+  }
+
+  public abstract class IntProperty extends android.util.Property {
+    ctor public IntProperty(java.lang.String);
+    method public final void set(T, java.lang.Integer);
+    method public abstract void setValue(T, int);
+  }
+
   public final class JsonReader implements java.io.Closeable {
     ctor public JsonReader(java.io.Reader);
     method public void beginArray() throws java.io.IOException;
diff --git a/api/system-current.txt b/api/system-current.txt
index 9db70e3..4118f33 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -20523,6 +20523,7 @@
   public class X509TrustManagerExtensions {
     ctor public X509TrustManagerExtensions(javax.net.ssl.X509TrustManager) throws java.lang.IllegalArgumentException;
     method public java.util.List<java.security.cert.X509Certificate> checkServerTrusted(java.security.cert.X509Certificate[], java.lang.String, java.lang.String) throws java.security.cert.CertificateException;
+    method public boolean isSameTrustConfiguration(java.lang.String, java.lang.String);
     method public boolean isUserAddedCertificate(java.security.cert.X509Certificate);
   }
 
@@ -36565,6 +36566,18 @@
   public deprecated class FloatMath {
   }
 
+  public abstract class FloatProperty extends android.util.Property {
+    ctor public FloatProperty(java.lang.String);
+    method public final void set(T, java.lang.Float);
+    method public abstract void setValue(T, float);
+  }
+
+  public abstract class IntProperty extends android.util.Property {
+    ctor public IntProperty(java.lang.String);
+    method public final void set(T, java.lang.Integer);
+    method public abstract void setValue(T, int);
+  }
+
   public final class JsonReader implements java.io.Closeable {
     ctor public JsonReader(java.io.Reader);
     method public void beginArray() throws java.io.IOException;
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 12780a8..7f33cb5 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -18,7 +18,7 @@
 
 package com.android.commands.am;
 
-import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
 import static android.app.ActivityManager.RESIZE_MODE_USER;
 
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 2960cdc..ab781bb 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -25,7 +25,6 @@
 import android.accounts.IAccountManager;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
-import android.app.IActivityManager;
 import android.app.PackageInstallObserver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -34,30 +33,25 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageInstaller;
 import android.content.pm.IPackageManager;
-import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
-import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.PermissionGroupInfo;
-import android.content.pm.PermissionInfo;
 import android.content.pm.UserInfo;
 import android.content.pm.VerificationParams;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IUserManager;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -80,11 +74,6 @@
 import java.io.OutputStream;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.WeakHashMap;
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -96,9 +85,6 @@
     IUserManager mUm;
     IAccountManager mAm;
 
-    private WeakHashMap<String, Resources> mResourceCache
-            = new WeakHashMap<String, Resources>();
-
     private String[] mArgs;
     private int mNextArg;
     private String mCurArgData;
@@ -275,10 +261,10 @@
             if (args.length == 1) {
                 if (args[0].equalsIgnoreCase("-l")) {
                     validCommand = true;
-                    return runListPackages(false);
-                } else if (args[0].equalsIgnoreCase("-lf")){
+                    return runShellCommand("package", new String[] { "list", "package" });
+                } else if (args[0].equalsIgnoreCase("-lf")) {
                     validCommand = true;
-                    return runListPackages(true);
+                    return runShellCommand("package", new String[] { "list", "package", "-f" });
                 }
             } else if (args.length == 2) {
                 if (args[0].equalsIgnoreCase("-p")) {
@@ -297,6 +283,22 @@
         }
     }
 
+    private int runShellCommand(String serviceName, String[] args) {
+        final HandlerThread handlerThread = new HandlerThread("results");
+        handlerThread.start();
+        try {
+            ServiceManager.getService(serviceName).shellCommand(
+                    FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+                    args, new ResultReceiver(new Handler(handlerThread.getLooper())));
+            return 0;
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        } finally {
+            handlerThread.quitSafely();
+        }
+        return -1;
+    }
+
     /**
      * Execute the list sub-command.
      *
@@ -308,462 +310,11 @@
      * pm list instrumentation
      */
     private int runList() {
-        String type = nextArg();
-        if (type == null) {
-            System.err.println("Error: didn't specify type of data to list");
-            return 1;
+        final String type = nextArg();
+        if ("users".equals(type)) {
+            return runShellCommand("user", new String[] { "list" });
         }
-        if ("package".equals(type) || "packages".equals(type)) {
-            return runListPackages(false);
-        } else if ("permission-groups".equals(type)) {
-            return runListPermissionGroups();
-        } else if ("permissions".equals(type)) {
-            return runListPermissions();
-        } else if ("features".equals(type)) {
-            return runListFeatures();
-        } else if ("libraries".equals(type)) {
-            return runListLibraries();
-        } else if ("instrumentation".equals(type)) {
-            return runListInstrumentation();
-        } else if ("users".equals(type)) {
-            return runListUsers();
-        } else {
-            System.err.println("Error: unknown list type '" + type + "'");
-            return 1;
-        }
-    }
-
-    /**
-     * Lists all the installed packages.
-     */
-    private int runListPackages(boolean showApplicationPackage) {
-        int getFlags = 0;
-        boolean listDisabled = false, listEnabled = false;
-        boolean listSystem = false, listThirdParty = false;
-        boolean listInstaller = false;
-        int userId = UserHandle.USER_SYSTEM;
-        try {
-            String opt;
-            while ((opt=nextOption()) != null) {
-                if (opt.equals("-l")) {
-                    // old compat
-                } else if (opt.equals("-lf")) {
-                    showApplicationPackage = true;
-                } else if (opt.equals("-f")) {
-                    showApplicationPackage = true;
-                } else if (opt.equals("-d")) {
-                    listDisabled = true;
-                } else if (opt.equals("-e")) {
-                    listEnabled = true;
-                } else if (opt.equals("-s")) {
-                    listSystem = true;
-                } else if (opt.equals("-3")) {
-                    listThirdParty = true;
-                } else if (opt.equals("-i")) {
-                    listInstaller = true;
-                } else if (opt.equals("--user")) {
-                    userId = Integer.parseInt(nextArg());
-                } else if (opt.equals("-u")) {
-                    getFlags |= PackageManager.GET_UNINSTALLED_PACKAGES;
-                } else {
-                    System.err.println("Error: Unknown option: " + opt);
-                    return 1;
-                }
-            }
-        } catch (RuntimeException ex) {
-            System.err.println("Error: " + ex.toString());
-            return 1;
-        }
-
-        String filter = nextArg();
-
-        try {
-            final List<PackageInfo> packages = getInstalledPackages(mPm, getFlags, userId);
-
-            int count = packages.size();
-            for (int p = 0 ; p < count ; p++) {
-                PackageInfo info = packages.get(p);
-                if (filter != null && !info.packageName.contains(filter)) {
-                    continue;
-                }
-                final boolean isSystem =
-                        (info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0;
-                if ((!listDisabled || !info.applicationInfo.enabled) &&
-                        (!listEnabled || info.applicationInfo.enabled) &&
-                        (!listSystem || isSystem) &&
-                        (!listThirdParty || !isSystem)) {
-                    System.out.print("package:");
-                    if (showApplicationPackage) {
-                        System.out.print(info.applicationInfo.sourceDir);
-                        System.out.print("=");
-                    }
-                    System.out.print(info.packageName);
-                    if (listInstaller) {
-                        System.out.print("  installer=");
-                        System.out.print(mPm.getInstallerPackageName(info.packageName));
-                    }
-                    System.out.println();
-                }
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    private List<PackageInfo> getInstalledPackages(IPackageManager pm, int flags, int userId)
-            throws RemoteException {
-        ParceledListSlice<PackageInfo> slice = pm.getInstalledPackages(flags, userId);
-        return slice.getList();
-    }
-
-    /**
-     * Lists all of the features supported by the current device.
-     *
-     * pm list features
-     */
-    private int runListFeatures() {
-        try {
-            List<FeatureInfo> list = new ArrayList<FeatureInfo>();
-            FeatureInfo[] rawList = mPm.getSystemAvailableFeatures();
-            for (int i=0; i<rawList.length; i++) {
-                list.add(rawList[i]);
-            }
-
-
-            // Sort by name
-            Collections.sort(list, new Comparator<FeatureInfo>() {
-                public int compare(FeatureInfo o1, FeatureInfo o2) {
-                    if (o1.name == o2.name) return 0;
-                    if (o1.name == null) return -1;
-                    if (o2.name == null) return 1;
-                    return o1.name.compareTo(o2.name);
-                }
-            });
-
-            int count = (list != null) ? list.size() : 0;
-            for (int p = 0; p < count; p++) {
-                FeatureInfo fi = list.get(p);
-                System.out.print("feature:");
-                if (fi.name != null) System.out.println(fi.name);
-                else System.out.println("reqGlEsVersion=0x"
-                        + Integer.toHexString(fi.reqGlEsVersion));
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    /**
-     * Lists all of the libraries supported by the current device.
-     *
-     * pm list libraries
-     */
-    private int runListLibraries() {
-        try {
-            List<String> list = new ArrayList<String>();
-            String[] rawList = mPm.getSystemSharedLibraryNames();
-            for (int i=0; i<rawList.length; i++) {
-                list.add(rawList[i]);
-            }
-
-
-            // Sort by name
-            Collections.sort(list, new Comparator<String>() {
-                public int compare(String o1, String o2) {
-                    if (o1 == o2) return 0;
-                    if (o1 == null) return -1;
-                    if (o2 == null) return 1;
-                    return o1.compareTo(o2);
-                }
-            });
-
-            int count = (list != null) ? list.size() : 0;
-            for (int p = 0; p < count; p++) {
-                String lib = list.get(p);
-                System.out.print("library:");
-                System.out.println(lib);
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    /**
-     * Lists all of the installed instrumentation, or all for a given package
-     *
-     * pm list instrumentation [package] [-f]
-     */
-    private int runListInstrumentation() {
-        int flags = 0;      // flags != 0 is only used to request meta-data
-        boolean showPackage = false;
-        String targetPackage = null;
-
-        try {
-            String opt;
-            while ((opt=nextArg()) != null) {
-                if (opt.equals("-f")) {
-                    showPackage = true;
-                } else if (opt.charAt(0) != '-') {
-                    targetPackage = opt;
-                } else {
-                    System.err.println("Error: Unknown option: " + opt);
-                    return 1;
-                }
-            }
-        } catch (RuntimeException ex) {
-            System.err.println("Error: " + ex.toString());
-            return 1;
-        }
-
-        try {
-            List<InstrumentationInfo> list = mPm.queryInstrumentation(targetPackage, flags);
-
-            // Sort by target package
-            Collections.sort(list, new Comparator<InstrumentationInfo>() {
-                public int compare(InstrumentationInfo o1, InstrumentationInfo o2) {
-                    return o1.targetPackage.compareTo(o2.targetPackage);
-                }
-            });
-
-            int count = (list != null) ? list.size() : 0;
-            for (int p = 0; p < count; p++) {
-                InstrumentationInfo ii = list.get(p);
-                System.out.print("instrumentation:");
-                if (showPackage) {
-                    System.out.print(ii.sourceDir);
-                    System.out.print("=");
-                }
-                ComponentName cn = new ComponentName(ii.packageName, ii.name);
-                System.out.print(cn.flattenToShortString());
-                System.out.print(" (target=");
-                System.out.print(ii.targetPackage);
-                System.out.println(")");
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    /**
-     * Lists all the known permission groups.
-     */
-    private int runListPermissionGroups() {
-        try {
-            List<PermissionGroupInfo> pgs = mPm.getAllPermissionGroups(0);
-
-            int count = pgs.size();
-            for (int p = 0 ; p < count ; p++) {
-                PermissionGroupInfo pgi = pgs.get(p);
-                System.out.print("permission group:");
-                System.out.println(pgi.name);
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    private String loadText(PackageItemInfo pii, int res, CharSequence nonLocalized) {
-        if (nonLocalized != null) {
-            return nonLocalized.toString();
-        }
-        if (res != 0) {
-            Resources r = getResources(pii);
-            if (r != null) {
-                try {
-                    return r.getString(res);
-                } catch (Resources.NotFoundException e) {
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Lists all the permissions in a group.
-     */
-    private int runListPermissions() {
-        try {
-            boolean labels = false;
-            boolean groups = false;
-            boolean userOnly = false;
-            boolean summary = false;
-            boolean dangerousOnly = false;
-            String opt;
-            while ((opt=nextOption()) != null) {
-                if (opt.equals("-f")) {
-                    labels = true;
-                } else if (opt.equals("-g")) {
-                    groups = true;
-                } else if (opt.equals("-s")) {
-                    groups = true;
-                    labels = true;
-                    summary = true;
-                } else if (opt.equals("-u")) {
-                    userOnly = true;
-                } else if (opt.equals("-d")) {
-                    dangerousOnly = true;
-                } else {
-                    System.err.println("Error: Unknown option: " + opt);
-                    return 1;
-                }
-            }
-
-            String grp = nextArg();
-            ArrayList<String> groupList = new ArrayList<String>();
-            if (groups) {
-                List<PermissionGroupInfo> infos =
-                        mPm.getAllPermissionGroups(0);
-                for (int i=0; i<infos.size(); i++) {
-                    groupList.add(infos.get(i).name);
-                }
-                groupList.add(null);
-            } else {
-                groupList.add(grp);
-            }
-
-            if (dangerousOnly) {
-                System.out.println("Dangerous Permissions:");
-                System.out.println("");
-                doListPermissions(groupList, groups, labels, summary,
-                        PermissionInfo.PROTECTION_DANGEROUS,
-                        PermissionInfo.PROTECTION_DANGEROUS);
-                if (userOnly) {
-                    System.out.println("Normal Permissions:");
-                    System.out.println("");
-                    doListPermissions(groupList, groups, labels, summary,
-                            PermissionInfo.PROTECTION_NORMAL,
-                            PermissionInfo.PROTECTION_NORMAL);
-                }
-            } else if (userOnly) {
-                System.out.println("Dangerous and Normal Permissions:");
-                System.out.println("");
-                doListPermissions(groupList, groups, labels, summary,
-                        PermissionInfo.PROTECTION_NORMAL,
-                        PermissionInfo.PROTECTION_DANGEROUS);
-            } else {
-                System.out.println("All Permissions:");
-                System.out.println("");
-                doListPermissions(groupList, groups, labels, summary,
-                        -10000, 10000);
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    private void doListPermissions(ArrayList<String> groupList,
-            boolean groups, boolean labels, boolean summary,
-            int startProtectionLevel, int endProtectionLevel)
-            throws RemoteException {
-        for (int i=0; i<groupList.size(); i++) {
-            String groupName = groupList.get(i);
-            String prefix = "";
-            if (groups) {
-                if (i > 0) System.out.println("");
-                if (groupName != null) {
-                    PermissionGroupInfo pgi = mPm.getPermissionGroupInfo(
-                            groupName, 0);
-                    if (summary) {
-                        Resources res = getResources(pgi);
-                        if (res != null) {
-                            System.out.print(loadText(pgi, pgi.labelRes,
-                                    pgi.nonLocalizedLabel) + ": ");
-                        } else {
-                            System.out.print(pgi.name + ": ");
-
-                        }
-                    } else {
-                        System.out.println((labels ? "+ " : "")
-                                + "group:" + pgi.name);
-                        if (labels) {
-                            System.out.println("  package:" + pgi.packageName);
-                            Resources res = getResources(pgi);
-                            if (res != null) {
-                                System.out.println("  label:"
-                                        + loadText(pgi, pgi.labelRes,
-                                                pgi.nonLocalizedLabel));
-                                System.out.println("  description:"
-                                        + loadText(pgi, pgi.descriptionRes,
-                                                pgi.nonLocalizedDescription));
-                            }
-                        }
-                    }
-                } else {
-                    System.out.println(((labels && !summary)
-                            ? "+ " : "") + "ungrouped:");
-                }
-                prefix = "  ";
-            }
-            List<PermissionInfo> ps = mPm.queryPermissionsByGroup(
-                    groupList.get(i), 0);
-            int count = ps.size();
-            boolean first = true;
-            for (int p = 0 ; p < count ; p++) {
-                PermissionInfo pi = ps.get(p);
-                if (groups && groupName == null && pi.group != null) {
-                    continue;
-                }
-                final int base = pi.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
-                if (base < startProtectionLevel
-                        || base > endProtectionLevel) {
-                    continue;
-                }
-                if (summary) {
-                    if (first) {
-                        first = false;
-                    } else {
-                        System.out.print(", ");
-                    }
-                    Resources res = getResources(pi);
-                    if (res != null) {
-                        System.out.print(loadText(pi, pi.labelRes,
-                                pi.nonLocalizedLabel));
-                    } else {
-                        System.out.print(pi.name);
-                    }
-                } else {
-                    System.out.println(prefix + (labels ? "+ " : "")
-                            + "permission:" + pi.name);
-                    if (labels) {
-                        System.out.println(prefix + "  package:" + pi.packageName);
-                        Resources res = getResources(pi);
-                        if (res != null) {
-                            System.out.println(prefix + "  label:"
-                                    + loadText(pi, pi.labelRes,
-                                            pi.nonLocalizedLabel));
-                            System.out.println(prefix + "  description:"
-                                    + loadText(pi, pi.descriptionRes,
-                                            pi.nonLocalizedDescription));
-                        }
-                        System.out.println(prefix + "  protectionLevel:"
-                                + PermissionInfo.protectionToString(pi.protectionLevel));
-                    }
-                }
-            }
-
-            if (summary) {
-                System.out.println("");
-            }
-        }
+        return runShellCommand("package", mArgs);
     }
 
     private int runPath() {
@@ -1467,29 +1018,6 @@
         }
     }
 
-    public int runListUsers() {
-        try {
-            IActivityManager am = ActivityManagerNative.getDefault();
-
-            List<UserInfo> users = mUm.getUsers(false);
-            if (users == null) {
-                System.err.println("Error: couldn't get users");
-                return 1;
-            } else {
-                System.out.println("Users:");
-                for (int i = 0; i < users.size(); i++) {
-                    String running = am.isUserRunning(users.get(i).id, false) ? " running" : "";
-                    System.out.println("\t" + users.get(i).toString() + running);
-                }
-                return 0;
-            }
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
     public int runGetMaxUsers() {
         System.out.println("Maximum supported users: " + UserManager.getMaxSupportedUsers());
         return 0;
@@ -1997,24 +1525,6 @@
         return 1;
     }
 
-    private Resources getResources(PackageItemInfo pii) {
-        Resources res = mResourceCache.get(pii.packageName);
-        if (res != null) return res;
-
-        try {
-            ApplicationInfo ai = mPm.getApplicationInfo(pii.packageName, 0, 0);
-            AssetManager am = new AssetManager();
-            am.addAssetPath(ai.publicSourceDir);
-            res = new Resources(am, null, null);
-            mResourceCache.put(pii.packageName, res);
-            return res;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return null;
-        }
-    }
-
     private static String checkAbiArgument(String abi) {
         if (TextUtils.isEmpty(abi)) {
             throw new IllegalArgumentException("Missing ABI argument");
@@ -2110,14 +1620,7 @@
     }
 
     private static int showUsage() {
-        System.err.println("usage: pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER]");
-        System.err.println("       pm list permission-groups");
-        System.err.println("       pm list permissions [-g] [-f] [-d] [-u] [GROUP]");
-        System.err.println("       pm list instrumentation [-f] [TARGET-PACKAGE]");
-        System.err.println("       pm list features");
-        System.err.println("       pm list libraries");
-        System.err.println("       pm list users");
-        System.err.println("       pm path [--user USER_ID] PACKAGE");
+        System.err.println("usage: pm path [--user USER_ID] PACKAGE");
         System.err.println("       pm dump PACKAGE");
         System.err.println("       pm install [-lrtsfd] [-i PACKAGE] [--user USER_ID] [PATH]");
         System.err.println("       pm install-create [-lrtsfdp] [-i PACKAGE] [-S BYTES]");
@@ -2151,34 +1654,8 @@
         System.err.println("       pm remove-user USER_ID");
         System.err.println("       pm get-max-users");
         System.err.println("");
-        System.err.println("pm list packages: prints all packages, optionally only");
-        System.err.println("  those whose package name contains the text in FILTER.  Options:");
-        System.err.println("    -f: see their associated file.");
-        System.err.println("    -d: filter to only show disbled packages.");
-        System.err.println("    -e: filter to only show enabled packages.");
-        System.err.println("    -s: filter to only show system packages.");
-        System.err.println("    -3: filter to only show third party packages.");
-        System.err.println("    -i: see the installer for the packages.");
-        System.err.println("    -u: also include uninstalled packages.");
-        System.err.println("");
-        System.err.println("pm list permission-groups: prints all known permission groups.");
-        System.err.println("");
-        System.err.println("pm list permissions: prints all known permissions, optionally only");
-        System.err.println("  those in GROUP.  Options:");
-        System.err.println("    -g: organize by group.");
-        System.err.println("    -f: print all information.");
-        System.err.println("    -s: short summary.");
-        System.err.println("    -d: only list dangerous permissions.");
-        System.err.println("    -u: list only the permissions users will see.");
-        System.err.println("");
-        System.err.println("pm list instrumentation: use to list all test packages; optionally");
-        System.err.println("  supply <TARGET-PACKAGE> to list the test packages for a particular");
-        System.err.println("  application.  Options:");
-        System.err.println("    -f: list the .apk file for the test package.");
-        System.err.println("");
-        System.err.println("pm list features: prints all features of the system.");
-        System.err.println("");
-        System.err.println("pm list users: prints all users on the system.");
+        System.err.println("NOTE: 'pm list' commands have moved! Run 'adb shell cmd package'");
+        System.err.println("  to display the new commands.");
         System.err.println("");
         System.err.println("pm path: print the path to the .apk of the given PACKAGE.");
         System.err.println("");
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2f0849f..472d97f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2728,8 +2728,8 @@
     /**
      * Called to move the window and its activity/task to a different stack container.
      * For example, a window can move between
-     * {@link android.app.ActivityManager#FULLSCREEN_WORKSPACE_STACK_ID} stack and
-     * {@link android.app.ActivityManager#FREEFORM_WORKSPACE_STACK_ID} stack.
+     * {@link android.app.ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} stack and
+     * {@link android.app.ActivityManager.StackId#FREEFORM_WORKSPACE_STACK_ID} stack.
      *
      * @param stackId stack Id to change to.
      * @hide
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index fce1b2e..b809baa 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -73,7 +73,6 @@
  */
 public class ActivityManager {
     private static String TAG = "ActivityManager";
-    private static boolean localLOGV = false;
 
     private static int gMaxRecentTasks = -1;
 
@@ -397,60 +396,112 @@
      */
     public static final int COMPAT_MODE_TOGGLE = 2;
 
-    /**
-     * Invalid stack ID.
-     * @hide
-     */
-    public static final int INVALID_STACK_ID = -1;
+    /** @hide */
+    public static class StackId {
+        /** Invalid stack ID. */
+        public static final int INVALID_STACK_ID = -1;
 
-    /**
-     * First static stack ID.
-     * @hide
-     */
-    public static final int FIRST_STATIC_STACK_ID = 0;
+        /** First static stack ID. */
+        public static final int FIRST_STATIC_STACK_ID = 0;
 
-    /**
-     * Home activity stack ID.
-     * @hide
-     */
-    public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;
+        /** Home activity stack ID. */
+        public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;
 
-    /**
-     * ID of stack where fullscreen activities are normally launched into.
-     * @hide
-     */
-    public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
+        /** ID of stack where fullscreen activities are normally launched into. */
+        public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
 
-    /**
-     * ID of stack where freeform/resized activities are normally launched into.
-     * @hide
-     */
-    public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
+        /** ID of stack where freeform/resized activities are normally launched into. */
+        public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
 
-    /**
-     * ID of stack that occupies a dedicated region of the screen.
-     * @hide
-     */
-    public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
+        /** ID of stack that occupies a dedicated region of the screen. */
+        public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
 
-    /**
-     * ID of stack that always on top (always visible) when it exist.
-     * Mainly used for this in Picture-in-Picture mode.
-     * @hide
-     */
-    public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
+        /** ID of stack that always on top (always visible) when it exist. */
+        public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
 
-    /**
-     * Last static stack stack ID.
-     * @hide
-     */
-    public static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID;
+        /** Last static stack stack ID. */
+        public static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID;
 
-    /**
-     * Start of ID range used by stacks that are created dynamically.
-     * @hide
-     */
-    public static final int FIRST_DYNAMIC_STACK_ID = LAST_STATIC_STACK_ID + 1;
+        /** Start of ID range used by stacks that are created dynamically. */
+        public static final int FIRST_DYNAMIC_STACK_ID = LAST_STATIC_STACK_ID + 1;
+
+        public static boolean isStaticStack(int stackId) {
+            return stackId >= FIRST_STATIC_STACK_ID && stackId <= LAST_STATIC_STACK_ID;
+        }
+
+        /**
+         * Returns true if the activities contained in the input stack display a shadow around
+         * their border.
+         */
+        public static boolean hasWindowShadow(int stackId) {
+            return stackId == FREEFORM_WORKSPACE_STACK_ID || stackId == PINNED_STACK_ID;
+        }
+
+        /**
+         * Returns true if the activities contained in the input stack display a decor view.
+         */
+        public static boolean hasWindowDecor(int stackId) {
+            return stackId == FREEFORM_WORKSPACE_STACK_ID;
+        }
+
+        /**
+         * Returns true if the tasks contained in the stack can be resized independently of the
+         * stack.
+         */
+        public static boolean isTaskResizeAllowed(int stackId) {
+            return stackId == FREEFORM_WORKSPACE_STACK_ID;
+        }
+
+        /**
+         * Returns true if the task bounds should persist across power cycles.
+         */
+        public static boolean persistTaskBounds(int stackId) {
+            return isStaticStack(stackId) &&
+                    stackId != DOCKED_STACK_ID && stackId != PINNED_STACK_ID;
+        }
+
+        /**
+         * Returns true if dynamic stacks are allowed to be visible behind the input stack.
+         */
+        public static boolean isDynamicStacksVisibleBehindAllowed(int stackId) {
+            return stackId == PINNED_STACK_ID;
+        }
+
+        /**
+         * Returns true if we try to maintain focus in the current stack when the top activity
+         * finishes.
+         */
+        public static boolean keepFocusInStackIfPossible(int stackId) {
+            return stackId == FREEFORM_WORKSPACE_STACK_ID
+                    || stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID;
+        }
+
+        /**
+         * Returns true if Stack size is affected by the docked stack changing size.
+         */
+        public static boolean isResizeableByDockedStack(int stackId) {
+            return isStaticStack(stackId) &&
+                    stackId != DOCKED_STACK_ID && stackId != PINNED_STACK_ID;
+        }
+
+        /**
+         * Returns true if the size of tasks in the input stack are affected by the docked stack
+         * changing size.
+         */
+        public static boolean isTaskResizeableByDockedStack(int stackId) {
+            return isStaticStack(stackId) && stackId != FREEFORM_WORKSPACE_STACK_ID
+                    && stackId != DOCKED_STACK_ID && stackId != PINNED_STACK_ID;
+        }
+
+        /**
+         * Returns true if the windows of tasks being moved to this stack should be preserved so
+         * there isn't a display gap.
+         */
+        public static boolean preserveWindowOnTaskMove(int stackId) {
+            return stackId == FULLSCREEN_WORKSPACE_STACK_ID
+                    || stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID;
+        }
+    }
 
     /**
      * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which
@@ -1016,6 +1067,12 @@
          */
         public int numActivities;
 
+        /**
+         * The bounds of the task.
+         * @hide
+         */
+        public Rect bounds;
+
         public RecentTaskInfo() {
         }
 
@@ -1053,6 +1110,12 @@
             ComponentName.writeToParcel(baseActivity, dest);
             ComponentName.writeToParcel(topActivity, dest);
             dest.writeInt(numActivities);
+            if (bounds != null) {
+                dest.writeInt(1);
+                bounds.writeToParcel(dest, 0);
+            } else {
+                dest.writeInt(0);
+            }
         }
 
         public void readFromParcel(Parcel source) {
@@ -1073,6 +1136,8 @@
             baseActivity = ComponentName.readFromParcel(source);
             topActivity = ComponentName.readFromParcel(source);
             numActivities = source.readInt();
+            bounds = source.readInt() > 0 ?
+                    Rect.CREATOR.createFromParcel(source) : null;
         }
 
         public static final Creator<RecentTaskInfo> CREATOR
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 09c0a6e..77a9795 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -653,7 +653,7 @@
             null, //WRITE_SETTINGS
             UserManager.DISALLOW_CREATE_WINDOWS, //SYSTEM_ALERT_WINDOW
             null, //ACCESS_NOTIFICATIONS
-            null, //CAMERA
+            UserManager.DISALLOW_CAMERA, //CAMERA
             UserManager.DISALLOW_RECORD_AUDIO, //RECORD_AUDIO
             null, //PLAY_AUDIO
             null, //READ_CLIPBOARD
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 49edff4..ffeb6ed 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3829,7 +3829,9 @@
         @Override
         public void purgeResources() {
             super.purgeResources();
-            if (mPicture != null && mPicture.isMutable()) {
+            if (mPicture != null &&
+                mPicture.isMutable() &&
+                mPicture.getAllocationByteCount() >= (128 * (1 << 10))) {
                 mPicture = mPicture.createAshmemBitmap();
             }
             if (mBigLargeIcon != null) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9e9d949..0fdf3d3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2271,6 +2271,8 @@
      * on the device, for this user. After setting this, no applications running as this user
      * will be able to access any cameras on the device.
      *
+     * <p>If the caller is device owner, then the restriction will be applied to all users.
+     *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA} to be able to call
      * this method; if it has not, a security exception will be thrown.
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 948ea1e..498ff81 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -71,10 +71,11 @@
      * Could be hours, could be days, who knows?
      *
      * @param packageName
+     * @param uidForAppId The uid of the app, which will be used for its app id
      * @param userId
      * @return
      */
-    public abstract boolean isAppIdle(String packageName, int userId);
+    public abstract boolean isAppIdle(String packageName, int uidForAppId, int userId);
 
     /**
      * Returns all of the uids for a given user where all packages associating with that uid
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 27ecf9f..c57fc89 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1125,7 +1125,7 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
-     * The device has professional audio level of functionality, performance, and acoustics.
+     * The device has professional audio level of functionality and performance.
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_AUDIO_PRO = "android.hardware.audio.pro";
diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java
index eb4ceda..25ef8b5 100644
--- a/core/java/android/net/http/X509TrustManagerExtensions.java
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -16,6 +16,8 @@
 
 package android.net.http;
 
+import android.annotation.SystemApi;
+
 import com.android.org.conscrypt.TrustManagerImpl;
 
 import java.security.cert.CertificateException;
@@ -80,4 +82,15 @@
     public boolean isUserAddedCertificate(X509Certificate cert) {
         return mDelegate.isUserAddedCertificate(cert);
     }
+
+    /**
+     * Returns {@code true} if the TrustManager uses the same trust configuration for the provided
+     * hostnames.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isSameTrustConfiguration(String hostname1, String hostname2) {
+        return true;
+    }
 }
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 1f3e9a7..56cb250 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -108,6 +108,13 @@
      */
     public static final String EXTRA_MAX_CHARGING_CURRENT = "max_charging_current";
 
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * Int value set to the maximum charging voltage supported by the charger in micro volts.
+     * {@hide}
+     */
+    public static final String EXTRA_MAX_CHARGING_VOLTAGE = "max_charging_voltage";
+
     // values for "status" field in the ACTION_BATTERY_CHANGED Intent
     public static final int BATTERY_STATUS_UNKNOWN = 1;
     public static final int BATTERY_STATUS_CHARGING = 2;
diff --git a/core/java/android/os/BatteryProperties.java b/core/java/android/os/BatteryProperties.java
index 29e868c..c3e0f24 100644
--- a/core/java/android/os/BatteryProperties.java
+++ b/core/java/android/os/BatteryProperties.java
@@ -23,6 +23,7 @@
     public boolean chargerUsbOnline;
     public boolean chargerWirelessOnline;
     public int maxChargingCurrent;
+    public int maxChargingVoltage;
     public int batteryStatus;
     public int batteryHealth;
     public boolean batteryPresent;
@@ -39,6 +40,7 @@
         chargerUsbOnline = other.chargerUsbOnline;
         chargerWirelessOnline = other.chargerWirelessOnline;
         maxChargingCurrent = other.maxChargingCurrent;
+        maxChargingVoltage = other.maxChargingVoltage;
         batteryStatus = other.batteryStatus;
         batteryHealth = other.batteryHealth;
         batteryPresent = other.batteryPresent;
@@ -58,6 +60,7 @@
         chargerUsbOnline = p.readInt() == 1 ? true : false;
         chargerWirelessOnline = p.readInt() == 1 ? true : false;
         maxChargingCurrent = p.readInt();
+        maxChargingVoltage = p.readInt();
         batteryStatus = p.readInt();
         batteryHealth = p.readInt();
         batteryPresent = p.readInt() == 1 ? true : false;
@@ -72,6 +75,7 @@
         p.writeInt(chargerUsbOnline ? 1 : 0);
         p.writeInt(chargerWirelessOnline ? 1 : 0);
         p.writeInt(maxChargingCurrent);
+        p.writeInt(maxChargingVoltage);
         p.writeInt(batteryStatus);
         p.writeInt(batteryHealth);
         p.writeInt(batteryPresent ? 1 : 0);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 2e31ab6..e892349 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -487,6 +487,16 @@
     public static final String DISALLOW_RECORD_AUDIO = "no_record_audio";
 
     /**
+     * Specifies if a user is not allowed to use the camera.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     * @hide
+     */
+    public static final String DISALLOW_CAMERA = "no_camera";
+
+    /**
      * Allows apps in the parent profile to handle web links from the managed profile.
      *
      * This user restriction has an effect only in a managed profile.
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index d7be6d8..b50cc79 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -36,7 +36,7 @@
      *
      * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
      */
-    public abstract void updateEffectiveUserRestrictionsRL(int userId);
+    public abstract void updateEffectiveUserRestrictionsLR(int userId);
 
     /**
      * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
@@ -44,7 +44,7 @@
      *
      * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
      */
-    public abstract void updateEffectiveUserRestrictionsForAllUsersRL();
+    public abstract void updateEffectiveUserRestrictionsForAllUsersLR();
 
     /**
      * Returns the "base" user restrictions.
diff --git a/core/java/android/util/FloatProperty.java b/core/java/android/util/FloatProperty.java
index a67b3cb..4aac196 100644
--- a/core/java/android/util/FloatProperty.java
+++ b/core/java/android/util/FloatProperty.java
@@ -15,18 +15,14 @@
  */
 package android.util;
 
-import android.util.Property;
-
 /**
  * An implementation of {@link android.util.Property} to be used specifically with fields of type
  * <code>float</code>. This type-specific subclass enables performance benefit by allowing
- * calls to a {@link #set(Object, Float) set()} function that takes the primitive
+ * calls to a {@link #setValue(Object, float) setValue()} function that takes the primitive
  * <code>float</code> type and avoids autoboxing and other overhead associated with the
  * <code>Float</code> class.
  *
  * @param <T> The class on which the Property is declared.
- *
- * @hide
  */
 public abstract class FloatProperty<T> extends Property<T, Float> {
 
@@ -35,7 +31,7 @@
     }
 
     /**
-     * A type-specific override of the {@link #set(Object, Float)} that is faster when dealing
+     * A type-specific variant of {@link #set(Object, Float)} that is faster when dealing
      * with fields of type <code>float</code>.
      */
     public abstract void setValue(T object, float value);
diff --git a/core/java/android/util/IntProperty.java b/core/java/android/util/IntProperty.java
index 17977ca..9e21ced 100644
--- a/core/java/android/util/IntProperty.java
+++ b/core/java/android/util/IntProperty.java
@@ -15,18 +15,14 @@
  */
 package android.util;
 
-import android.util.Property;
-
 /**
  * An implementation of {@link android.util.Property} to be used specifically with fields of type
  * <code>int</code>. This type-specific subclass enables performance benefit by allowing
- * calls to a {@link #set(Object, Integer) set()} function that takes the primitive
+ * calls to a {@link #setValue(Object, int) setValue()} function that takes the primitive
  * <code>int</code> type and avoids autoboxing and other overhead associated with the
  * <code>Integer</code> class.
  *
  * @param <T> The class on which the Property is declared.
- *
- * @hide
  */
 public abstract class IntProperty<T> extends Property<T, Integer> {
 
@@ -35,7 +31,7 @@
     }
 
     /**
-     * A type-specific override of the {@link #set(Object, Integer)} that is faster when dealing
+     * A type-specific variant of {@link #set(Object, Integer)} that is faster when dealing
      * with fields of type <code>int</code>.
      */
     public abstract void setValue(T object, int value);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index f18b35d..c01c6df 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -201,6 +201,11 @@
      */
     void setScreenCaptureDisabled(int userId, boolean disabled);
 
+    /**
+     * Cancels the window transitions for the given task.
+     */
+    void cancelTaskWindowTransition(int taskId);
+
     // These can only be called with the SET_ORIENTATION permission.
     /**
      * Update the current screen rotation based on the current state of
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 017364a..1be2f95 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -96,6 +96,23 @@
             out Rect outOutsets, out Configuration outConfig, out Surface outSurface);
 
     /**
+     *  Position a window relative to it's parent (attached) window without triggering
+     *  a full relayout. This action may be deferred until a given frame number
+     *  for the parent window appears. This allows for synchronizing movement of a child
+     *  to repainting the contents of the parent.
+     *
+     *  @param window The window being modified. Must be attached to a parent window
+     *  or this call will fail.
+     *  @param x The new x position
+     *  @param y The new y position
+     *  @param deferTransactionUntilFrame Frame number from our parent (attached) to
+     *  defer this action until.
+     *  @param outFrame Rect in which is placed the new position/size on screen.
+     */
+    void repositionChild(IWindow childWindow, int x, int y, long deferTransactionUntilFrame,
+            out Rect outFrame);
+
+    /**
      * If a call to relayout() asked to have the surface destroy deferred,
      * it must call this once it is okay to destroy that surface.
      */
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 6de4d3e..394660f 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -56,6 +56,8 @@
     private static native int nativeGetWidth(long nativeObject);
     private static native int nativeGetHeight(long nativeObject);
 
+    private static native long nativeGetNextFrameNumber(long nativeObject);
+
     public static final Parcelable.Creator<Surface> CREATOR =
             new Parcelable.Creator<Surface>() {
         @Override
@@ -220,6 +222,18 @@
     }
 
     /**
+     * Returns the next frame number which will be dequeued for rendering.
+     * Intended for use with SurfaceFlinger's deferred transactions API.
+     *
+     * @hide
+     */
+    public long getNextFrameNumber() {
+        synchronized (mLock) {
+            return nativeGetNextFrameNumber(mNativeObject);
+        }
+    }
+
+    /**
      * Returns true if the consumer of this Surface is running behind the producer.
      *
      * @return True if the consumer is more than one buffer ahead of the producer.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index bcf9b2c..b58c68f 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -81,6 +81,9 @@
     private static native boolean nativeSetActiveConfig(IBinder displayToken, int id);
     private static native void nativeSetDisplayPowerMode(
             IBinder displayToken, int mode);
+    private static native void nativeDeferTransactionUntil(long nativeObject,
+            IBinder handle, long frame);
+    private static native IBinder nativeGetHandle(long nativeObject);
 
 
     private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -358,6 +361,14 @@
         nativeCloseTransaction();
     }
 
+    public void deferTransactionUntil(IBinder handle, long frame) {
+        nativeDeferTransactionUntil(mNativeObject, handle, frame);
+    }
+
+    public IBinder getHandle() {
+        return nativeGetHandle(mNativeObject);
+    }
+
     /** flag the transaction as an animation */
     public static void setAnimationTransaction() {
         nativeSetAnimationTransaction();
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index db68c29..dddea21 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -448,11 +448,10 @@
         final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
         final boolean visibleChanged = mVisible != mRequestedVisible;
         final boolean layoutSizeChanged = getWidth() != mLayout.width || getHeight() != mLayout.height;
+        final boolean positionChanged = mLeft != mLocation[0] || mTop != mLocation[1];
 
         if (force || creating || formatChanged || sizeChanged || visibleChanged
-            || mLeft != mLocation[0] || mTop != mLocation[1]
             || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded || layoutSizeChanged) {
-
             if (DEBUG) Log.i(TAG, "Changes: creating=" + creating
                     + " format=" + formatChanged + " size=" + sizeChanged
                     + " visible=" + visibleChanged
@@ -616,11 +615,22 @@
                     mSession.performDeferredDestroy(mWindow);
                 }
             } catch (RemoteException ex) {
+                Log.e(TAG, "Exception from relayout", ex);
             }
             if (DEBUG) Log.v(
                 TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
                 " w=" + mLayout.width + " h=" + mLayout.height +
                 ", frame=" + mSurfaceFrame);
+        } else if (positionChanged) { // Only the position has changed
+            mLeft = mLocation[0];
+            mTop = mLocation[1];
+            try {
+                mSession.repositionChild(mWindow, mLeft, mTop,
+                        viewRoot != null ? viewRoot.getNextFrameNumber() : -1,
+                        mWinFrame);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Exception from relayout", ex);
+            }
         }
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8b804e8..cd8c084 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5455,7 +5455,7 @@
      * @hide
      */
     protected boolean performButtonActionOnTouchDown(MotionEvent event) {
-        if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE &&
+        if (event.isFromSource(InputDevice.SOURCE_MOUSE) &&
             (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
             showContextMenu(event.getX(), event.getY());
             mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e17bdd7..faeb353 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6607,6 +6607,20 @@
         }
     }
 
+    long getNextFrameNumber() {
+        long frameNumber = -1;
+        if (mSurfaceHolder != null) {
+            mSurfaceHolder.mSurfaceLock.lock();
+        }
+        if (mSurface.isValid()) {
+            frameNumber =  mSurface.getNextFrameNumber();
+        }
+        if (mSurfaceHolder != null) {
+            mSurfaceHolder.mSurfaceLock.unlock();
+        }
+        return frameNumber;
+    }
+
     class TakenSurfaceHolder extends BaseSurfaceHolder {
         @Override
         public boolean onAllowLockCanvas() {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 5d11c8b..8259372 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -552,8 +552,8 @@
         /**
          * Called to move the window and its activity/task to a different stack container.
          * For example, a window can move between
-         * {@link android.app.ActivityManager#FULLSCREEN_WORKSPACE_STACK_ID} stack and
-         * {@link android.app.ActivityManager#FREEFORM_WORKSPACE_STACK_ID} stack.
+         * {@link android.app.ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} stack and
+         * {@link android.app.ActivityManager.StackId#FREEFORM_WORKSPACE_STACK_ID} stack.
          *
          * @param stackId stack Id to change to.
          */
diff --git a/core/java/android/webkit/WebResourceRequest.java b/core/java/android/webkit/WebResourceRequest.java
index 23e9a0d..ab93505 100644
--- a/core/java/android/webkit/WebResourceRequest.java
+++ b/core/java/android/webkit/WebResourceRequest.java
@@ -40,9 +40,9 @@
     boolean isForMainFrame();
 
     /**
-     * Gets whether the request was a result of a redirect.
+     * Gets whether the request was a result of a server-side redirect.
      *
-     * @return whether the request was a result of a redirect.
+     * @return whether the request was a result of a server-side redirect.
      */
     boolean isRedirect();
 
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 584deff..cb18b49 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -96,27 +96,49 @@
         public MissingWebViewPackageException(Exception e) { super(e); }
     }
 
-    public static String getWebViewPackageName() {
-        return AppGlobals.getInitialApplication().getString(
-                com.android.internal.R.string.config_webViewPackageName);
+    /** @hide */
+    public static String[] getWebViewPackageNames() {
+        return AppGlobals.getInitialApplication().getResources().getStringArray(
+                com.android.internal.R.array.config_webViewPackageNames);
     }
 
-    private static PackageInfo fetchPackageInfo() {
+    // TODO (gsennton) remove when committing webview xts test change
+    public static String getWebViewPackageName() {
+        String[] webViewPackageNames = getWebViewPackageNames();
+        return webViewPackageNames[webViewPackageNames.length-1];
+    }
+
+    /**
+     * Return the package info of the first package in the webview priority list that contains
+     * webview.
+     *
+     * @hide
+     */
+    public static PackageInfo findPreferredWebViewPackage() {
         PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
-        try {
-            return pm.getPackageInfo(getWebViewPackageName(), PackageManager.GET_META_DATA);
-        } catch (PackageManager.NameNotFoundException e) {
-            throw new MissingWebViewPackageException(e);
+
+        for (String packageName : getWebViewPackageNames()) {
+            try {
+                PackageInfo packageInfo = pm.getPackageInfo(packageName,
+                    PackageManager.GET_META_DATA);
+                ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+
+                // If the correct flag is set the package contains webview.
+                if (getWebViewLibrary(applicationInfo) != null) {
+                    return packageInfo;
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+            }
         }
+        throw new MissingWebViewPackageException("Could not find a loadable WebView package");
     }
 
     // throws MissingWebViewPackageException
     private static ApplicationInfo getWebViewApplicationInfo() {
-        if (sPackageInfo == null) {
-            return fetchPackageInfo().applicationInfo;
-        } else {
+        if (sPackageInfo == null)
+            return findPreferredWebViewPackage().applicationInfo;
+        else
             return sPackageInfo.applicationInfo;
-        }
     }
 
     private static String getWebViewLibrary(ApplicationInfo ai) {
@@ -134,7 +156,12 @@
      * name is the same as the one providing the webview.
      */
     public static int loadWebViewNativeLibraryFromPackage(String packageName) {
-        sPackageInfo = fetchPackageInfo();
+        try {
+            sPackageInfo = findPreferredWebViewPackage();
+        } catch (MissingWebViewPackageException e) {
+            return LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
+        }
+
         if (packageName != null && packageName.equals(sPackageInfo.packageName)) {
             return loadNativeLibrary();
         }
@@ -180,7 +207,7 @@
     private static Class<WebViewFactoryProvider> getProviderClass() {
         try {
             // First fetch the package info so we can log the webview package version.
-            sPackageInfo = fetchPackageInfo();
+            sPackageInfo = findPreferredWebViewPackage();
             Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " +
                 sPackageInfo.versionName + " (code " + sPackageInfo.versionCode + ")");
 
diff --git a/core/java/com/android/internal/os/InstallerConnection.java b/core/java/com/android/internal/os/InstallerConnection.java
index 13d046e..830da79 100644
--- a/core/java/com/android/internal/os/InstallerConnection.java
+++ b/core/java/com/android/internal/os/InstallerConnection.java
@@ -21,6 +21,8 @@
 import android.os.SystemClock;
 import android.util.Slog;
 
+import com.android.internal.util.Preconditions;
+
 import libcore.io.IoUtils;
 import libcore.io.Streams;
 
@@ -42,11 +44,22 @@
     private OutputStream mOut;
     private LocalSocket mSocket;
 
+    private volatile Object mWarnIfHeld;
+
     private final byte buf[] = new byte[1024];
 
     public InstallerConnection() {
     }
 
+    /**
+     * Yell loudly if someone tries making future calls while holding a lock on
+     * the given object.
+     */
+    public void setWarnIfHeld(Object warnIfHeld) {
+        Preconditions.checkState(mWarnIfHeld == null);
+        mWarnIfHeld = Preconditions.checkNotNull(warnIfHeld);
+    }
+
     public synchronized String transact(String cmd) {
         if (!connect()) {
             Slog.e(TAG, "connection failed");
@@ -84,6 +97,11 @@
     }
 
     public int execute(String cmd) {
+        if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
+            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
+                    + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
+        }
+
         String res = transact(cmd);
         try {
             return Integer.parseInt(res);
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index b3bd46d..1bce585 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -16,11 +16,8 @@
 
 package com.android.internal.policy;
 
-import static android.app.ActivityManager.FIRST_DYNAMIC_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.view.View.MeasureSpec.AT_MOST;
 import static android.view.View.MeasureSpec.EXACTLY;
 import static android.view.View.MeasureSpec.getMode;
@@ -30,6 +27,7 @@
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
+import android.app.ActivityManager.StackId;
 import android.app.ActivityManagerNative;
 import android.app.SearchManager;
 import android.os.Build;
@@ -737,9 +735,9 @@
             if (mWorkspaceId != workspaceId) {
                 mWorkspaceId = workspaceId;
                 // We might have to change the kind of surface before we do anything else.
-                mNonClientDecorView.phoneWindowUpdated(hasNonClientDecor(mWorkspaceId),
-                        nonClientDecorHasShadow(mWorkspaceId));
-                mDecor.enableNonClientDecor(hasNonClientDecor(workspaceId));
+                mNonClientDecorView.phoneWindowUpdated(StackId.hasWindowDecor(mWorkspaceId),
+                        StackId.hasWindowShadow(mWorkspaceId));
+                mDecor.enableNonClientDecor(StackId.hasWindowDecor(workspaceId));
             }
         }
     }
@@ -3735,7 +3733,7 @@
          * @return Returns true when the window has a shadow created by the non client decor.
          **/
         private boolean windowHasShadow() {
-            return windowHasNonClientDecor() && nonClientDecorHasShadow(mWindow.mWorkspaceId);
+            return windowHasNonClientDecor() && StackId.hasWindowShadow(mWindow.mWorkspaceId);
         }
 
         void setWindow(PhoneWindow phoneWindow) {
@@ -4234,7 +4232,7 @@
         mWorkspaceId = getWorkspaceId();
         // Only a non floating application window on one of the allowed workspaces can get a non
         // client decor.
-        if (!isFloating() && isApplication && mWorkspaceId < FIRST_DYNAMIC_STACK_ID) {
+        if (!isFloating() && isApplication && StackId.isStaticStack(mWorkspaceId)) {
             // Dependent on the brightness of the used title we either use the
             // dark or the light button frame.
             if (nonClientDecorView == null) {
@@ -4250,12 +4248,13 @@
                             R.layout.non_client_decor_light, null);
                 }
             }
-            nonClientDecorView.setPhoneWindow(this, hasNonClientDecor(mWorkspaceId),
-                    nonClientDecorHasShadow(mWorkspaceId), getResizingBackgroundDrawable(),
+            nonClientDecorView.setPhoneWindow(this, StackId.hasWindowDecor(mWorkspaceId),
+                    StackId.hasWindowShadow(mWorkspaceId), getResizingBackgroundDrawable(),
                     mDecor.getContext().getDrawable(R.drawable.non_client_decor_title_focused));
         }
         // Tell the decor if it has a visible non client decor.
-        mDecor.enableNonClientDecor(nonClientDecorView != null && hasNonClientDecor(mWorkspaceId));
+        mDecor.enableNonClientDecor(
+                nonClientDecorView != null&& StackId.hasWindowDecor(mWorkspaceId));
 
         return nonClientDecorView;
     }
@@ -5428,24 +5427,6 @@
         return workspaceId;
     }
 
-    /**
-     * Determines if the window should show a non client decor for the workspace it is in.
-     * @param workspaceId The Id of the workspace which contains this window.
-     * @Return Returns true if the window should show a non client decor.
-     **/
-    private static boolean hasNonClientDecor(int workspaceId) {
-        return workspaceId == FREEFORM_WORKSPACE_STACK_ID;
-    }
-
-    /**
-     * Determines if the window should show a shadow or not, dependent on the workspace.
-     * @param workspaceId The Id of the workspace which contains this window.
-     * @Return Returns true if the window should show a shadow.
-     **/
-    private static boolean nonClientDecorHasShadow(int workspaceId) {
-        return workspaceId == FREEFORM_WORKSPACE_STACK_ID || workspaceId == PINNED_STACK_ID;
-    }
-
     @Override
     public void setTheme(int resid) {
         mTheme = resid;
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
index be9be11..de542b1 100644
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ b/core/java/com/android/internal/widget/NonClientDecorView.java
@@ -16,8 +16,9 @@
 
 package com.android.internal.widget;
 
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+
 import android.content.Context;
-import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Looper;
@@ -332,8 +333,7 @@
         Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
         if (callback != null) {
             try {
-                callback.changeWindowStack(
-                        android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID);
+                callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
             } catch (RemoteException ex) {
                 Log.e(TAG, "Cannot change task workspace.");
             }
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index e6c7c2b..703a9bd 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -28,6 +28,7 @@
 #include <cutils/ashmem.h>
 
 #define DEBUG_PARCEL 0
+#define ASHMEM_BITMAP_MIN_SIZE (128 * (1 << 10))
 
 namespace android {
 
@@ -993,7 +994,7 @@
 
     // Map the bitmap in place from the ashmem region if possible otherwise copy.
     Bitmap* nativeBitmap;
-    if (blob.fd() >= 0 && (blob.isMutable() || !isMutable)) {
+    if (blob.fd() >= 0 && (blob.isMutable() || !isMutable) && (size >= ASHMEM_BITMAP_MIN_SIZE)) {
 #if DEBUG_PARCEL
         ALOGD("Bitmap.createFromParcel: mapped contents of %s bitmap from %s blob "
                 "(fds %s)",
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 41aa9ca..0a8ae2b 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -722,33 +722,33 @@
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gParcelMethods[] = {
-    {"nativeDataSize",            "(J)I", (void*)android_os_Parcel_dataSize},
-    {"nativeDataAvail",           "(J)I", (void*)android_os_Parcel_dataAvail},
-    {"nativeDataPosition",        "(J)I", (void*)android_os_Parcel_dataPosition},
-    {"nativeDataCapacity",        "(J)I", (void*)android_os_Parcel_dataCapacity},
-    {"nativeSetDataSize",         "(JI)J", (void*)android_os_Parcel_setDataSize},
-    {"nativeSetDataPosition",     "(JI)V", (void*)android_os_Parcel_setDataPosition},
-    {"nativeSetDataCapacity",     "(JI)V", (void*)android_os_Parcel_setDataCapacity},
+    {"nativeDataSize",            "!(J)I", (void*)android_os_Parcel_dataSize},
+    {"nativeDataAvail",           "!(J)I", (void*)android_os_Parcel_dataAvail},
+    {"nativeDataPosition",        "!(J)I", (void*)android_os_Parcel_dataPosition},
+    {"nativeDataCapacity",        "!(J)I", (void*)android_os_Parcel_dataCapacity},
+    {"nativeSetDataSize",         "!(JI)J", (void*)android_os_Parcel_setDataSize},
+    {"nativeSetDataPosition",     "!(JI)V", (void*)android_os_Parcel_setDataPosition},
+    {"nativeSetDataCapacity",     "!(JI)V", (void*)android_os_Parcel_setDataCapacity},
 
-    {"nativePushAllowFds",        "(JZ)Z", (void*)android_os_Parcel_pushAllowFds},
-    {"nativeRestoreAllowFds",     "(JZ)V", (void*)android_os_Parcel_restoreAllowFds},
+    {"nativePushAllowFds",        "!(JZ)Z", (void*)android_os_Parcel_pushAllowFds},
+    {"nativeRestoreAllowFds",     "!(JZ)V", (void*)android_os_Parcel_restoreAllowFds},
 
     {"nativeWriteByteArray",      "(J[BII)V", (void*)android_os_Parcel_writeNative},
     {"nativeWriteBlob",           "(J[BII)V", (void*)android_os_Parcel_writeBlob},
-    {"nativeWriteInt",            "(JI)V", (void*)android_os_Parcel_writeInt},
-    {"nativeWriteLong",           "(JJ)V", (void*)android_os_Parcel_writeLong},
-    {"nativeWriteFloat",          "(JF)V", (void*)android_os_Parcel_writeFloat},
-    {"nativeWriteDouble",         "(JD)V", (void*)android_os_Parcel_writeDouble},
+    {"nativeWriteInt",            "!(JI)V", (void*)android_os_Parcel_writeInt},
+    {"nativeWriteLong",           "!(JJ)V", (void*)android_os_Parcel_writeLong},
+    {"nativeWriteFloat",          "!(JF)V", (void*)android_os_Parcel_writeFloat},
+    {"nativeWriteDouble",         "!(JD)V", (void*)android_os_Parcel_writeDouble},
     {"nativeWriteString",         "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString},
     {"nativeWriteStrongBinder",   "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
     {"nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)J", (void*)android_os_Parcel_writeFileDescriptor},
 
     {"nativeCreateByteArray",     "(J)[B", (void*)android_os_Parcel_createByteArray},
     {"nativeReadBlob",            "(J)[B", (void*)android_os_Parcel_readBlob},
-    {"nativeReadInt",             "(J)I", (void*)android_os_Parcel_readInt},
-    {"nativeReadLong",            "(J)J", (void*)android_os_Parcel_readLong},
-    {"nativeReadFloat",           "(J)F", (void*)android_os_Parcel_readFloat},
-    {"nativeReadDouble",          "(J)D", (void*)android_os_Parcel_readDouble},
+    {"nativeReadInt",             "!(J)I", (void*)android_os_Parcel_readInt},
+    {"nativeReadLong",            "!(J)J", (void*)android_os_Parcel_readLong},
+    {"nativeReadFloat",           "!(J)F", (void*)android_os_Parcel_readFloat},
+    {"nativeReadDouble",          "!(J)D", (void*)android_os_Parcel_readDouble},
     {"nativeReadString",          "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString},
     {"nativeReadStrongBinder",    "(J)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},
     {"nativeReadFileDescriptor",  "(J)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_readFileDescriptor},
@@ -765,7 +765,7 @@
     {"nativeMarshall",            "(J)[B", (void*)android_os_Parcel_marshall},
     {"nativeUnmarshall",          "(J[BII)J", (void*)android_os_Parcel_unmarshall},
     {"nativeAppendFrom",          "(JJII)J", (void*)android_os_Parcel_appendFrom},
-    {"nativeHasFileDescriptors",  "(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
+    {"nativeHasFileDescriptors",  "!(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
     {"nativeWriteInterfaceToken", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
     {"nativeEnforceInterface",    "(JLjava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},
 
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index c94bc64..55b7e7e 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -2156,9 +2156,9 @@
         (void*) android_content_AssetManager_readAsset },
     { "seekAsset",      "(JJI)J",
         (void*) android_content_AssetManager_seekAsset },
-    { "getAssetLength", "(J)J",
+    { "getAssetLength", "!(J)J",
         (void*) android_content_AssetManager_getAssetLength },
-    { "getAssetRemainingLength", "(J)J",
+    { "getAssetRemainingLength", "!(J)J",
         (void*) android_content_AssetManager_getAssetRemainingLength },
     { "addAssetPathNative", "(Ljava/lang/String;Z)I",
         (void*) android_content_AssetManager_addAssetPath },
@@ -2174,25 +2174,25 @@
         (void*) android_content_AssetManager_getLocales },
     { "getSizeConfigurations", "()[Landroid/content/res/Configuration;",
         (void*) android_content_AssetManager_getSizeConfigurations },
-    { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIII)V",
+    { "setConfiguration", "!(IILjava/lang/String;IIIIIIIIIIIIII)V",
         (void*) android_content_AssetManager_setConfiguration },
-    { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+    { "getResourceIdentifier","!(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
         (void*) android_content_AssetManager_getResourceIdentifier },
-    { "getResourceName","(I)Ljava/lang/String;",
+    { "getResourceName","!(I)Ljava/lang/String;",
         (void*) android_content_AssetManager_getResourceName },
-    { "getResourcePackageName","(I)Ljava/lang/String;",
+    { "getResourcePackageName","!(I)Ljava/lang/String;",
         (void*) android_content_AssetManager_getResourcePackageName },
-    { "getResourceTypeName","(I)Ljava/lang/String;",
+    { "getResourceTypeName","!(I)Ljava/lang/String;",
         (void*) android_content_AssetManager_getResourceTypeName },
-    { "getResourceEntryName","(I)Ljava/lang/String;",
+    { "getResourceEntryName","!(I)Ljava/lang/String;",
         (void*) android_content_AssetManager_getResourceEntryName },
-    { "loadResourceValue","(ISLandroid/util/TypedValue;Z)I",
+    { "loadResourceValue","!(ISLandroid/util/TypedValue;Z)I",
         (void*) android_content_AssetManager_loadResourceValue },
-    { "loadResourceBagValue","(IILandroid/util/TypedValue;Z)I",
+    { "loadResourceBagValue","!(IILandroid/util/TypedValue;Z)I",
         (void*) android_content_AssetManager_loadResourceBagValue },
-    { "getStringBlockCount","()I",
+    { "getStringBlockCount","!()I",
         (void*) android_content_AssetManager_getStringBlockCount },
-    { "getNativeStringBlock","(I)J",
+    { "getNativeStringBlock","!(I)J",
         (void*) android_content_AssetManager_getNativeStringBlock },
     { "getCookieName","(I)Ljava/lang/String;",
         (void*) android_content_AssetManager_getCookieName },
@@ -2210,21 +2210,21 @@
         (void*) android_content_AssetManager_copyTheme },
     { "clearTheme", "(J)V",
         (void*) android_content_AssetManager_clearTheme },
-    { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I",
+    { "loadThemeAttributeValue", "!(JILandroid/util/TypedValue;Z)I",
         (void*) android_content_AssetManager_loadThemeAttributeValue },
-    { "getThemeChangingConfigurations", "(J)I",
+    { "getThemeChangingConfigurations", "!(J)I",
         (void*) android_content_AssetManager_getThemeChangingConfigurations },
     { "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V",
         (void*) android_content_AssetManager_dumpTheme },
-    { "applyStyle","(JIIJ[I[I[I)Z",
+    { "applyStyle","!(JIIJ[I[I[I)Z",
         (void*) android_content_AssetManager_applyStyle },
-    { "resolveAttrs","(JII[I[I[I[I)Z",
+    { "resolveAttrs","!(JII[I[I[I[I)Z",
         (void*) android_content_AssetManager_resolveAttrs },
-    { "retrieveAttributes","(J[I[I[I)Z",
+    { "retrieveAttributes","!(J[I[I[I)Z",
         (void*) android_content_AssetManager_retrieveAttributes },
-    { "getArraySize","(I)I",
+    { "getArraySize","!(I)I",
         (void*) android_content_AssetManager_getArraySize },
-    { "retrieveArray","(I[I)I",
+    { "retrieveArray","!(I[I)I",
         (void*) android_content_AssetManager_retrieveArray },
 
     // XML files.
@@ -2234,11 +2234,11 @@
     // Arrays.
     { "getArrayStringResource","(I)[Ljava/lang/String;",
         (void*) android_content_AssetManager_getArrayStringResource },
-    { "getArrayStringInfo","(I)[I",
+    { "getArrayStringInfo","!(I)[I",
         (void*) android_content_AssetManager_getArrayStringInfo },
-    { "getArrayIntResource","(I)[I",
+    { "getArrayIntResource","!(I)[I",
         (void*) android_content_AssetManager_getArrayIntResource },
-    { "getStyleAttributes","(I)[I",
+    { "getStyleAttributes","!(I)[I",
         (void*) android_content_AssetManager_getStyleAttributes },
 
     // Bookkeeping.
diff --git a/core/jni/android_util_XmlBlock.cpp b/core/jni/android_util_XmlBlock.cpp
index 7ae51c8..a15c23c 100644
--- a/core/jni/android_util_XmlBlock.cpp
+++ b/core/jni/android_util_XmlBlock.cpp
@@ -372,37 +372,37 @@
             (void*) android_content_XmlBlock_nativeGetStringBlock },
     { "nativeCreateParseState",     "(J)J",
             (void*) android_content_XmlBlock_nativeCreateParseState },
-    { "nativeNext",                 "(J)I",
+    { "nativeNext",                 "!(J)I",
             (void*) android_content_XmlBlock_nativeNext },
-    { "nativeGetNamespace",         "(J)I",
+    { "nativeGetNamespace",         "!(J)I",
             (void*) android_content_XmlBlock_nativeGetNamespace },
-    { "nativeGetName",              "(J)I",
+    { "nativeGetName",              "!(J)I",
             (void*) android_content_XmlBlock_nativeGetName },
-    { "nativeGetText",              "(J)I",
+    { "nativeGetText",              "!(J)I",
             (void*) android_content_XmlBlock_nativeGetText },
-    { "nativeGetLineNumber",        "(J)I",
+    { "nativeGetLineNumber",        "!(J)I",
             (void*) android_content_XmlBlock_nativeGetLineNumber },
-    { "nativeGetAttributeCount",    "(J)I",
+    { "nativeGetAttributeCount",    "!(J)I",
             (void*) android_content_XmlBlock_nativeGetAttributeCount },
-    { "nativeGetAttributeNamespace","(JI)I",
+    { "nativeGetAttributeNamespace","!(JI)I",
             (void*) android_content_XmlBlock_nativeGetAttributeNamespace },
-    { "nativeGetAttributeName",     "(JI)I",
+    { "nativeGetAttributeName",     "!(JI)I",
             (void*) android_content_XmlBlock_nativeGetAttributeName },
-    { "nativeGetAttributeResource", "(JI)I",
+    { "nativeGetAttributeResource", "!(JI)I",
             (void*) android_content_XmlBlock_nativeGetAttributeResource },
-    { "nativeGetAttributeDataType", "(JI)I",
+    { "nativeGetAttributeDataType", "!(JI)I",
             (void*) android_content_XmlBlock_nativeGetAttributeDataType },
-    { "nativeGetAttributeData",    "(JI)I",
+    { "nativeGetAttributeData",    "!(JI)I",
             (void*) android_content_XmlBlock_nativeGetAttributeData },
-    { "nativeGetAttributeStringValue", "(JI)I",
+    { "nativeGetAttributeStringValue", "!(JI)I",
             (void*) android_content_XmlBlock_nativeGetAttributeStringValue },
-    { "nativeGetAttributeIndex",    "(JLjava/lang/String;Ljava/lang/String;)I",
+    { "nativeGetAttributeIndex",    "!(JLjava/lang/String;Ljava/lang/String;)I",
             (void*) android_content_XmlBlock_nativeGetAttributeIndex },
-    { "nativeGetIdAttribute",      "(J)I",
+    { "nativeGetIdAttribute",      "!(J)I",
             (void*) android_content_XmlBlock_nativeGetIdAttribute },
-    { "nativeGetClassAttribute",   "(J)I",
+    { "nativeGetClassAttribute",   "!(J)I",
             (void*) android_content_XmlBlock_nativeGetClassAttribute },
-    { "nativeGetStyleAttribute",   "(J)I",
+    { "nativeGetStyleAttribute",   "!(J)I",
             (void*) android_content_XmlBlock_nativeGetStyleAttribute },
     { "nativeDestroyParseState",    "(J)V",
             (void*) android_content_XmlBlock_nativeDestroyParseState },
diff --git a/core/jni/android_view_DisplayListCanvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp
index b64acc3..9b41eb3 100644
--- a/core/jni/android_view_DisplayListCanvas.cpp
+++ b/core/jni/android_view_DisplayListCanvas.cpp
@@ -151,10 +151,10 @@
         // not in the emulator
         return JNI_TRUE;
     }
-    // In the emulator this property will be set to 1 when hardware GLES is
+    // In the emulator this property will be set > 0 when OpenGL ES 2.0 is
     // enabled, 0 otherwise. On old emulator versions it will be undefined.
     property_get("ro.kernel.qemu.gles", prop, "0");
-    return atoi(prop) == 1 ? JNI_TRUE : JNI_FALSE;
+    return atoi(prop) > 0 ? JNI_TRUE : JNI_FALSE;
 }
 
 // ----------------------------------------------------------------------------
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index da96b93..ff51e4e 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -460,6 +460,10 @@
     anw->query(anw, NATIVE_WINDOW_HEIGHT, &value);
     return value;
 }
+static jlong nativeGetNextFrameNumber(JNIEnv *env, jclass clazz, jlong nativeObject) {
+    Surface* surface = reinterpret_cast<Surface*>(nativeObject);
+    return surface->getNextFrameNumber();
+}
 
 namespace uirenderer {
 
@@ -536,6 +540,7 @@
             (void*)nativeWriteToParcel },
     {"nativeGetWidth", "(J)I", (void*)nativeGetWidth },
     {"nativeGetHeight", "(J)I", (void*)nativeGetHeight },
+    {"nativeGetNextFrameNumber", "(J)J", (void*)nativeGetNextFrameNumber },
 
     // HWUI context
     {"nHwuiCreate", "(JJ)J", (void*) hwui::create },
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 931ad54..1dfe40a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -571,6 +571,21 @@
     return JNI_TRUE;
 }
 
+
+static void nativeDeferTransactionUntil(JNIEnv* env, jclass clazz, jlong nativeObject,
+        jobject handleObject, jlong frameNumber) {
+    auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+    sp<IBinder> handle = ibinderForJavaObject(env, handleObject);
+
+    ctrl->deferTransactionUntil(handle, frameNumber);
+}
+
+static jobject nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) {
+    auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+
+    return javaObjectForIBinder(env, ctrl->getHandle());
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod sSurfaceControlMethods[] = {
@@ -638,6 +653,10 @@
             (void*)nativeGetAnimationFrameStats },
     {"nativeSetDisplayPowerMode", "(Landroid/os/IBinder;I)V",
             (void*)nativeSetDisplayPowerMode },
+    {"nativeDeferTransactionUntil", "(JLandroid/os/IBinder;J)V",
+            (void*)nativeDeferTransactionUntil },
+    {"nativeGetHandle", "(J)Landroid/os/IBinder;",
+            (void*)nativeGetHandle }
 };
 
 int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5828829..561bcbc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2127,7 +2127,7 @@
     <!-- Allows an application to grant specific permissions.
          @hide -->
     <permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS"
-        android:protectionLevel="signature|installer" />
+        android:protectionLevel="signature|installer|verifier" />
 
     <!-- Allows an app that has this permission and the permissions to install packages
          to request certain runtime permissions to be granted at installation.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 400c822..76f7062 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2130,8 +2130,10 @@
         string that's stored in 8-bit unpacked format) characters.-->
     <bool translatable="false" name="config_sms_decode_gsm_8bit_data">false</bool>
 
-    <!-- Package name providing WebView implementation. -->
-    <string name="config_webViewPackageName" translatable="false">com.android.webview</string>
+    <!-- List of package names (ordered by preference) providing WebView implementations. -->
+    <string-array name="config_webViewPackageNames" translatable="false">
+      <item>com.android.webview</item>
+    </string-array>
 
     <!-- If EMS is not supported, framework breaks down EMS into single segment SMS
          and adds page info " x/y". This config is used to set which carrier doesn't
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index dd42c22..bfb0d10 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -367,11 +367,6 @@
 
     <dimen name="resolver_max_width">480dp</dimen>
 
-    <!-- @deprecated Use config_windowOutsetBottom instead.
-         Size of the offset applied to the position of the circular mask. This
-         is only used on circular displays. In the case where there is no
-         "chin", this will default to 0 -->
-    <dimen name="circular_display_mask_offset">0px</dimen>
     <!-- Amount to reduce the size of the circular mask by (to compensate for
          aliasing effects). This is only used on circular displays. -->
     <dimen name="circular_display_mask_thickness">1px</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d1932fc..ba28e81 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -445,7 +445,6 @@
   <java-symbol type="dimen" name="notification_large_icon_circle_padding" />
   <java-symbol type="dimen" name="notification_badge_size" />
   <java-symbol type="dimen" name="immersive_mode_cling_width" />
-  <java-symbol type="dimen" name="circular_display_mask_offset" />
   <java-symbol type="dimen" name="accessibility_magnification_indicator_width" />
   <java-symbol type="dimen" name="circular_display_mask_thickness" />
 
@@ -2019,7 +2018,7 @@
   <java-symbol type="attr" name="actionModeWebSearchDrawable" />
   <java-symbol type="string" name="websearch" />
   <java-symbol type="drawable" name="ic_media_video_poster" />
-  <java-symbol type="string" name="config_webViewPackageName" />
+  <java-symbol type="array" name="config_webViewPackageNames" />
 
   <!-- From SubtitleView -->
   <java-symbol type="dimen" name="subtitle_corner_radius" />
diff --git a/core/tests/benchmarks/src/android/content/res/ResourcesBenchmark.java b/core/tests/benchmarks/src/android/content/res/ResourcesBenchmark.java
new file mode 100644
index 0000000..3638473
--- /dev/null
+++ b/core/tests/benchmarks/src/android/content/res/ResourcesBenchmark.java
@@ -0,0 +1,84 @@
+/*
+ * 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 android.content.res;
+
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import com.android.internal.R;
+import com.google.caliper.SimpleBenchmark;
+
+import org.xmlpull.v1.XmlPullParser;
+
+public class ResourcesBenchmark extends SimpleBenchmark {
+
+    private AssetManager mAsset;
+    private Resources mRes;
+
+    private int mTextId;
+    private int mColorId;
+    private int mIntegerId;
+    private int mLayoutId;
+
+    @Override
+    protected void setUp() {
+        mAsset = new AssetManager();
+        mAsset.addAssetPath("/system/framework/framework-res.apk");
+        mRes = new Resources(mAsset, null, null);
+
+        mTextId = mRes.getIdentifier("cancel", "string", "android");
+        mColorId = mRes.getIdentifier("transparent", "color", "android");
+        mIntegerId = mRes.getIdentifier("config_shortAnimTime", "integer", "android");
+        mLayoutId = mRes.getIdentifier("two_line_list_item", "layout", "android");
+    }
+
+    @Override
+    protected void tearDown() {
+        mAsset.close();
+    }
+
+    public void timeGetString(int reps) {
+        for (int i = 0; i < reps; i++) {
+            mRes.getText(mTextId);
+        }
+    }
+
+    public void timeGetColor(int reps) {
+        for (int i = 0; i < reps; i++) {
+            mRes.getColor(mColorId, null);
+        }
+    }
+
+    public void timeGetInteger(int reps) {
+        for (int i = 0; i < reps; i++) {
+            mRes.getInteger(mIntegerId);
+        }
+    }
+
+    public void timeGetLayoutAndTraverse(int reps) throws Exception {
+        for (int i = 0; i < reps; i++) {
+            final XmlResourceParser parser = mRes.getLayout(mLayoutId);
+            try {
+                while (parser.next() != XmlPullParser.END_DOCUMENT) {
+                    // Walk the entire tree
+                }
+            } finally {
+                parser.close();
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 3c10368..6903b7b 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -136,7 +136,9 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="android.widget.TextViewActivity" android:label="TextViewActivity">
+        <activity android:name="android.widget.TextViewActivity"
+                android:label="TextViewActivity"
+                android:screenOrientation="portrait">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 6a76a27..bb51570 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -36,7 +36,6 @@
 
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.OrientationUtil;
 import android.view.KeyEvent;
 
 /**
@@ -44,16 +43,13 @@
  */
 public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextViewActivity>{
 
-    private OrientationUtil mOrientationUtil;
-
     public TextViewActivityTest() {
         super(TextViewActivity.class);
     }
 
     @Override
     public void setUp() {
-        mOrientationUtil = OrientationUtil.initializeAndStartActivityIfNotStarted(this);
-        mOrientationUtil.setPortraitOrientation();
+        getActivity();
     }
 
     @SmallTest
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index 4f56965..4d2037b 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -812,8 +812,7 @@
             setTileModeY(parseTileMode(tileModeY));
         }
 
-        final int densityDpi = r.getDisplayMetrics().densityDpi;
-        state.mTargetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+        state.mTargetDensity = Drawable.resolveDensity(r, 0);
     }
 
     @Override
@@ -975,13 +974,7 @@
      * after inflating or applying a theme.
      */
     private void updateLocalState(Resources res) {
-        if (res != null) {
-            final int densityDpi = res.getDisplayMetrics().densityDpi;
-            mTargetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
-        } else {
-            mTargetDensity = mBitmapState.mTargetDensity;
-        }
-
+        mTargetDensity = resolveDensity(res, mBitmapState.mTargetDensity);
         mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, mBitmapState.mTintMode);
         computeBitmapSize();
     }
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index ff28777..39d13df 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1079,8 +1079,7 @@
         // to the compatibility density only to have them scaled back up when
         // drawn to the screen.
         if (opts == null) opts = new BitmapFactory.Options();
-        opts.inScreenDensity = res != null
-                ? res.getDisplayMetrics().noncompatDensityDpi : DisplayMetrics.DENSITY_DEVICE;
+        opts.inScreenDensity = Drawable.resolveDensity(res, 0);
         Bitmap  bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);
         if (bm != null) {
             byte[] np = bm.getNinePatchChunk();
@@ -1338,6 +1337,65 @@
     }
 
     /**
+     * Scales a floating-point pixel value from the source density to the
+     * target density.
+     *
+     * @param pixels the pixel value for use in source density
+     * @param sourceDensity the source density
+     * @param targetDensity the target density
+     * @return the scaled pixel value for use in target density
+     */
+    static float scaleFromDensity(float pixels, int sourceDensity, int targetDensity) {
+        return pixels * targetDensity / sourceDensity;
+    }
+
+    /**
+     * Scales a pixel value from the source density to the target density,
+     * optionally handling the resulting pixel value as a size rather than an
+     * offset.
+     * <p>
+     * A size conversion involves rounding the base value and ensuring that
+     * a non-zero base value is at least one pixel in size.
+     * <p>
+     * An offset conversion involves simply truncating the base value to an
+     * integer.
+     *
+     * @param pixels the pixel value for use in source density
+     * @param sourceDensity the source density
+     * @param targetDensity the target density
+     * @param isSize {@code true} to handle the resulting scaled value as a
+     *               size, or {@code false} to handle it as an offset
+     * @return the scaled pixel value for use in target density
+     */
+    static int scaleFromDensity(
+            int pixels, int sourceDensity, int targetDensity, boolean isSize) {
+        if (pixels == 0 || sourceDensity == targetDensity) {
+            return pixels;
+        }
+
+        final float result = pixels * targetDensity / (float) sourceDensity;
+        if (!isSize) {
+            return (int) result;
+        }
+
+        final int rounded = Math.round(result);
+        if (rounded != 0) {
+            return rounded;
+        } else if (pixels == 0) {
+            return 0;
+        } else if (pixels > 0) {
+            return 1;
+        } else {
+            return -1;
+        }
+    }
+
+    static int resolveDensity(@NonNull Resources r, int parentDensity) {
+        final int densityDpi = r == null ? parentDensity : r.getDisplayMetrics().densityDpi;
+        return densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+    }
+
+    /**
      * Parses a {@link android.graphics.PorterDuff.Mode} from a tintMode
      * attribute's enum value.
      *
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index ac72734..b2044e1 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -701,13 +701,8 @@
         DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
                 Resources res) {
             mOwner = owner;
-
-            final Resources sourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null);
-            mSourceRes = sourceRes;
-
-            final int densityDpi = sourceRes == null ? 0 : sourceRes.getDisplayMetrics().densityDpi;
-            final int sourceDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
-            mDensity = sourceDensity;
+            mSourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null);
+            mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
 
             if (orig != null) {
                 mChangingConfigurations = orig.mChangingConfigurations;
@@ -731,7 +726,7 @@
                 mHasTintList = orig.mHasTintList;
                 mHasTintMode = orig.mHasTintMode;
 
-                if (orig.mDensity == sourceDensity) {
+                if (orig.mDensity == mDensity) {
                     if (orig.mCheckedPadding) {
                         mConstantPadding = new Rect(orig.mConstantPadding);
                         mCheckedPadding = true;
@@ -903,13 +898,11 @@
 
             // The density may have changed since the last update (if any). Any
             // dimension-type attributes will need their default values scaled.
-            final int densityDpi = res.getDisplayMetrics().densityDpi;
-            final int newSourceDensity = densityDpi == 0 ?
-                    DisplayMetrics.DENSITY_DEFAULT : densityDpi;
-            final int oldSourceDensity = mDensity;
-            mDensity = newSourceDensity;
+            final int targetDensity = Drawable.resolveDensity(res, mDensity);
+            final int sourceDensity = mDensity;
+            mDensity = targetDensity;
 
-            if (oldSourceDensity != newSourceDensity) {
+            if (sourceDensity != targetDensity) {
                 mCheckedConstantSize = false;
                 mCheckedPadding = false;
             }
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index d7fd8a5..4be86ef 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -17,11 +17,13 @@
 package android.graphics.drawable;
 
 import android.annotation.ColorInt;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
@@ -40,6 +42,7 @@
 import android.graphics.Shader;
 import android.graphics.SweepGradient;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.TypedValue;
 
@@ -1136,10 +1139,14 @@
     }
 
     @Override
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
+        super.inflate(r, parser, attrs, theme);
+
+        mGradientState.setDensity(Drawable.resolveDensity(r, 0));
+
         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawable);
-        super.inflateWithAttributes(r, parser, a, R.styleable.GradientDrawable_visible);
         updateStateFromTypedArray(a);
         a.recycle();
 
@@ -1149,7 +1156,7 @@
     }
 
     @Override
-    public void applyTheme(Theme t) {
+    public void applyTheme(@NonNull Theme t) {
         super.applyTheme(t);
 
         final GradientState state = mGradientState;
@@ -1157,6 +1164,8 @@
             return;
         }
 
+        state.setDensity(Drawable.resolveDensity(t.getResources(), 0));
+
         if (state.mThemeAttrs != null) {
             final TypedArray a = t.resolveAttributes(
                     state.mThemeAttrs, R.styleable.GradientDrawable);
@@ -1668,7 +1677,7 @@
     @Override
     public Drawable mutate() {
         if (!mMutated && super.mutate() == this) {
-            mGradientState = new GradientState(mGradientState);
+            mGradientState = new GradientState(mGradientState, null);
             updateLocalState(null);
             mMutated = true;
         }
@@ -1723,6 +1732,8 @@
         ColorStateList mTint = null;
         PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
 
+        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
+
         int[] mThemeAttrs;
         int[] mAttrSize;
         int[] mAttrGradient;
@@ -1736,55 +1747,145 @@
             setGradientColors(gradientColors);
         }
 
-        public GradientState(GradientState state) {
-            mChangingConfigurations = state.mChangingConfigurations;
-            mShape = state.mShape;
-            mGradient = state.mGradient;
-            mAngle = state.mAngle;
-            mOrientation = state.mOrientation;
-            mSolidColors = state.mSolidColors;
-            if (state.mGradientColors != null) {
-                mGradientColors = state.mGradientColors.clone();
+        public GradientState(@NonNull GradientState orig, @Nullable Resources res) {
+            mChangingConfigurations = orig.mChangingConfigurations;
+            mShape = orig.mShape;
+            mGradient = orig.mGradient;
+            mAngle = orig.mAngle;
+            mOrientation = orig.mOrientation;
+            mSolidColors = orig.mSolidColors;
+            if (orig.mGradientColors != null) {
+                mGradientColors = orig.mGradientColors.clone();
             }
-            if (state.mPositions != null) {
-                mPositions = state.mPositions.clone();
+            if (orig.mPositions != null) {
+                mPositions = orig.mPositions.clone();
             }
-            mStrokeColors = state.mStrokeColors;
-            mStrokeWidth = state.mStrokeWidth;
-            mStrokeDashWidth = state.mStrokeDashWidth;
-            mStrokeDashGap = state.mStrokeDashGap;
-            mRadius = state.mRadius;
-            if (state.mRadiusArray != null) {
-                mRadiusArray = state.mRadiusArray.clone();
+            mStrokeColors = orig.mStrokeColors;
+            mStrokeWidth = orig.mStrokeWidth;
+            mStrokeDashWidth = orig.mStrokeDashWidth;
+            mStrokeDashGap = orig.mStrokeDashGap;
+            mRadius = orig.mRadius;
+            if (orig.mRadiusArray != null) {
+                mRadiusArray = orig.mRadiusArray.clone();
             }
-            if (state.mPadding != null) {
-                mPadding = new Rect(state.mPadding);
+            if (orig.mPadding != null) {
+                mPadding = new Rect(orig.mPadding);
             }
-            mWidth = state.mWidth;
-            mHeight = state.mHeight;
-            mInnerRadiusRatio = state.mInnerRadiusRatio;
-            mThicknessRatio = state.mThicknessRatio;
-            mInnerRadius = state.mInnerRadius;
-            mThickness = state.mThickness;
-            mDither = state.mDither;
-            mOpticalInsets = state.mOpticalInsets;
-            mCenterX = state.mCenterX;
-            mCenterY = state.mCenterY;
-            mGradientRadius = state.mGradientRadius;
-            mGradientRadiusType = state.mGradientRadiusType;
-            mUseLevel = state.mUseLevel;
-            mUseLevelForShape = state.mUseLevelForShape;
-            mOpaqueOverBounds = state.mOpaqueOverBounds;
-            mOpaqueOverShape = state.mOpaqueOverShape;
-            mTint = state.mTint;
-            mTintMode = state.mTintMode;
-            mThemeAttrs = state.mThemeAttrs;
-            mAttrSize = state.mAttrSize;
-            mAttrGradient = state.mAttrGradient;
-            mAttrSolid = state.mAttrSolid;
-            mAttrStroke = state.mAttrStroke;
-            mAttrCorners = state.mAttrCorners;
-            mAttrPadding = state.mAttrPadding;
+            mWidth = orig.mWidth;
+            mHeight = orig.mHeight;
+            mInnerRadiusRatio = orig.mInnerRadiusRatio;
+            mThicknessRatio = orig.mThicknessRatio;
+            mInnerRadius = orig.mInnerRadius;
+            mThickness = orig.mThickness;
+            mDither = orig.mDither;
+            mOpticalInsets = orig.mOpticalInsets;
+            mCenterX = orig.mCenterX;
+            mCenterY = orig.mCenterY;
+            mGradientRadius = orig.mGradientRadius;
+            mGradientRadiusType = orig.mGradientRadiusType;
+            mUseLevel = orig.mUseLevel;
+            mUseLevelForShape = orig.mUseLevelForShape;
+            mOpaqueOverBounds = orig.mOpaqueOverBounds;
+            mOpaqueOverShape = orig.mOpaqueOverShape;
+            mTint = orig.mTint;
+            mTintMode = orig.mTintMode;
+            mThemeAttrs = orig.mThemeAttrs;
+            mAttrSize = orig.mAttrSize;
+            mAttrGradient = orig.mAttrGradient;
+            mAttrSolid = orig.mAttrSolid;
+            mAttrStroke = orig.mAttrStroke;
+            mAttrCorners = orig.mAttrCorners;
+            mAttrPadding = orig.mAttrPadding;
+
+            mDensity = Drawable.resolveDensity(res, orig.mDensity);
+            if (orig.mDensity != mDensity) {
+                applyDensityScaling(orig.mDensity, mDensity);
+            }
+        }
+
+        /**
+         * Sets the constant state density.
+         * <p>
+         * If the density has been previously set, dispatches the change to
+         * subclasses so that density-dependent properties may be scaled as
+         * necessary.
+         *
+         * @param targetDensity the new constant state density
+         */
+        public final void setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                final int sourceDensity = mDensity;
+                mDensity = targetDensity;
+
+                applyDensityScaling(sourceDensity, targetDensity);
+            }
+        }
+
+        private void applyDensityScaling(int sourceDensity, int targetDensity) {
+            if (mInnerRadius > 0) {
+                mInnerRadius = Drawable.scaleFromDensity(
+                        mInnerRadius, sourceDensity, targetDensity, true);
+            }
+            if (mThickness > 0) {
+                mThickness = Drawable.scaleFromDensity(
+                        mThickness, sourceDensity, targetDensity, true);
+            }
+            if (mOpticalInsets != Insets.NONE) {
+                final int left = Drawable.scaleFromDensity(
+                        mOpticalInsets.left, sourceDensity, targetDensity, true);
+                final int top = Drawable.scaleFromDensity(
+                        mOpticalInsets.top, sourceDensity, targetDensity, true);
+                final int right = Drawable.scaleFromDensity(
+                        mOpticalInsets.right, sourceDensity, targetDensity, true);
+                final int bottom = Drawable.scaleFromDensity(
+                        mOpticalInsets.bottom, sourceDensity, targetDensity, true);
+                mOpticalInsets = Insets.of(left, top, right, bottom);
+            }
+            if (mPadding != null) {
+                mPadding.left = Drawable.scaleFromDensity(
+                        mPadding.left, sourceDensity, targetDensity, false);
+                mPadding.top = Drawable.scaleFromDensity(
+                        mPadding.top, sourceDensity, targetDensity, false);
+                mPadding.right = Drawable.scaleFromDensity(
+                        mPadding.right, sourceDensity, targetDensity, false);
+                mPadding.bottom = Drawable.scaleFromDensity(
+                        mPadding.bottom, sourceDensity, targetDensity, false);
+            }
+            if (mRadius > 0) {
+                mRadius = Drawable.scaleFromDensity(mRadius, sourceDensity, targetDensity);
+            }
+            if (mRadiusArray != null) {
+                mRadiusArray[0] = Drawable.scaleFromDensity(
+                        (int) mRadiusArray[0], sourceDensity, targetDensity, true);
+                mRadiusArray[1] = Drawable.scaleFromDensity(
+                        (int) mRadiusArray[1], sourceDensity, targetDensity, true);
+                mRadiusArray[2] = Drawable.scaleFromDensity(
+                        (int) mRadiusArray[2], sourceDensity, targetDensity, true);
+                mRadiusArray[3] = Drawable.scaleFromDensity(
+                        (int) mRadiusArray[3], sourceDensity, targetDensity, true);
+            }
+            if (mStrokeWidth > 0) {
+                mStrokeWidth = Drawable.scaleFromDensity(
+                        mStrokeWidth, sourceDensity, targetDensity, true);
+            }
+            if (mStrokeDashWidth > 0) {
+                mStrokeDashWidth = Drawable.scaleFromDensity(
+                        mStrokeDashGap, sourceDensity, targetDensity);
+            }
+            if (mStrokeDashGap > 0) {
+                mStrokeDashGap = Drawable.scaleFromDensity(
+                        mStrokeDashGap, sourceDensity, targetDensity);
+            }
+            if (mGradientRadiusType == RADIUS_TYPE_PIXELS) {
+                mGradientRadius = Drawable.scaleFromDensity(
+                        mGradientRadius, sourceDensity, targetDensity);
+            }
+            if (mWidth > 0) {
+                mWidth = Drawable.scaleFromDensity(mWidth, sourceDensity, targetDensity, true);
+            }
+            if (mHeight > 0) {
+                mHeight = Drawable.scaleFromDensity(mHeight, sourceDensity, targetDensity, true);
+            }
         }
 
         @Override
@@ -1805,8 +1906,18 @@
         }
 
         @Override
-        public Drawable newDrawable(Resources res) {
-            return new GradientDrawable(this, res);
+        public Drawable newDrawable(@Nullable Resources res) {
+            // If this drawable is being created for a different density,
+            // just create a new constant state and call it a day.
+            final GradientState state;
+            final int density = Drawable.resolveDensity(res, mDensity);
+            if (density != mDensity) {
+                state = new GradientState(this, res);
+            } else {
+                state = this;
+            }
+
+            return new GradientDrawable(state, res);
         }
 
         @Override
@@ -1913,7 +2024,7 @@
      *
      * @param state Constant state from which the drawable inherits
      */
-    private GradientDrawable(GradientState state, Resources res) {
+    private GradientDrawable(@NonNull GradientState state, @Nullable Resources res) {
         mGradientState = state;
 
         updateLocalState(res);
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 26232a9..44d7530 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -382,7 +382,9 @@
      * @hide
      */
     public void convertToAshmem() {
-        if (mType == TYPE_BITMAP && getBitmap().isMutable()) {
+        if (mType == TYPE_BITMAP &&
+            getBitmap().isMutable() &&
+            getBitmap().getAllocationByteCount() >= (128 * (1 << 10))) {
             setBitmap(getBitmap().createAshmemBitmap());
         }
     }
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 4368fe9..1ede105 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -170,8 +170,7 @@
 
         // The density may have changed since the last update. This will
         // apply scaling to any existing constant state properties.
-        final int densityDpi = r.getDisplayMetrics().densityDpi;
-        final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+        final int density = Drawable.resolveDensity(r, 0);
         state.setDensity(density);
 
         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable);
@@ -200,8 +199,7 @@
             return;
         }
 
-        final int densityDpi = t.getResources().getDisplayMetrics().densityDpi;
-        final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+        final int density = Drawable.resolveDensity(t.getResources(), 0);
         state.setDensity(density);
 
         if (state.mThemeAttrs != null) {
@@ -1774,7 +1772,9 @@
             final Drawable clone;
             if (dr != null) {
                 final ConstantState cs = dr.getConstantState();
-                if (res != null) {
+                if (cs == null) {
+                    clone = dr;
+                } else if (res != null) {
                     clone = cs.newDrawable(res);
                 } else {
                     clone = cs.newDrawable();
@@ -1800,9 +1800,7 @@
             mGravity = orig.mGravity;
             mId = orig.mId;
 
-            final int densityDpi = res == null ? orig.mDensity : res.getDisplayMetrics().densityDpi;
-            mDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
-
+            mDensity = Drawable.resolveDensity(res, orig.mDensity);
             if (orig.mDensity != mDensity) {
                 applyDensityScaling(orig.mDensity, mDensity);
             }
@@ -1823,21 +1821,21 @@
         }
 
         private void applyDensityScaling(int sourceDensity, int targetDensity) {
-            mInsetL = Bitmap.scaleFromDensity(mInsetL, sourceDensity, targetDensity);
-            mInsetT = Bitmap.scaleFromDensity(mInsetT, sourceDensity, targetDensity);
-            mInsetR = Bitmap.scaleFromDensity(mInsetR, sourceDensity, targetDensity);
-            mInsetB = Bitmap.scaleFromDensity(mInsetB, sourceDensity, targetDensity);
+            mInsetL = Drawable.scaleFromDensity(mInsetL, sourceDensity, targetDensity, false);
+            mInsetT = Drawable.scaleFromDensity(mInsetT, sourceDensity, targetDensity, false);
+            mInsetR = Drawable.scaleFromDensity(mInsetR, sourceDensity, targetDensity, false);
+            mInsetB = Drawable.scaleFromDensity(mInsetB, sourceDensity, targetDensity, false);
             if (mInsetS != UNDEFINED_INSET) {
-                mInsetS = Bitmap.scaleFromDensity(mInsetS, sourceDensity, targetDensity);
+                mInsetS = Drawable.scaleFromDensity(mInsetS, sourceDensity, targetDensity, false);
             }
             if (mInsetE != UNDEFINED_INSET) {
-                mInsetE = Bitmap.scaleFromDensity(mInsetE, sourceDensity, targetDensity);
+                mInsetE = Drawable.scaleFromDensity(mInsetE, sourceDensity, targetDensity, false);
             }
             if (mWidth > 0) {
-                mWidth = Bitmap.scaleFromDensity(mWidth, sourceDensity, targetDensity);
+                mWidth = Drawable.scaleFromDensity(mWidth, sourceDensity, targetDensity, true);
             }
             if (mHeight > 0) {
-                mHeight = Bitmap.scaleFromDensity(mHeight, sourceDensity, targetDensity);
+                mHeight = Drawable.scaleFromDensity(mHeight, sourceDensity, targetDensity, true);
             }
         }
     }
@@ -1874,16 +1872,7 @@
 
         LayerState(@Nullable LayerState orig, @NonNull LayerDrawable owner,
                 @Nullable Resources res) {
-            final int densityDpi;
-            if (res != null) {
-                densityDpi = res.getDisplayMetrics().densityDpi;
-            } else if (orig != null) {
-                densityDpi = orig.mDensity;
-            } else {
-                densityDpi = 0;
-            }
-
-            mDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+            mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
 
             if (orig != null) {
                 final ChildDrawable[] origChildDrawable = orig.mChildren;
@@ -1939,28 +1928,28 @@
 
         private void applyDensityScaling(int sourceDensity, int targetDensity) {
             if (mPaddingLeft > 0) {
-                mPaddingLeft = Bitmap.scaleFromDensity(
-                        mPaddingLeft, sourceDensity, targetDensity);
+                mPaddingLeft = Drawable.scaleFromDensity(
+                        mPaddingLeft, sourceDensity, targetDensity, false);
             }
             if (mPaddingTop > 0) {
-                mPaddingTop = Bitmap.scaleFromDensity(
-                        mPaddingTop, sourceDensity, targetDensity);
+                mPaddingTop = Drawable.scaleFromDensity(
+                        mPaddingTop, sourceDensity, targetDensity, false);
             }
             if (mPaddingRight > 0) {
-                mPaddingRight = Bitmap.scaleFromDensity(
-                        mPaddingRight, sourceDensity, targetDensity);
+                mPaddingRight = Drawable.scaleFromDensity(
+                        mPaddingRight, sourceDensity, targetDensity, false);
             }
             if (mPaddingBottom > 0) {
-                mPaddingBottom = Bitmap.scaleFromDensity(
-                        mPaddingBottom, sourceDensity, targetDensity);
+                mPaddingBottom = Drawable.scaleFromDensity(
+                        mPaddingBottom, sourceDensity, targetDensity, false);
             }
             if (mPaddingStart > 0) {
-                mPaddingStart = Bitmap.scaleFromDensity(
-                        mPaddingStart, sourceDensity, targetDensity);
+                mPaddingStart = Drawable.scaleFromDensity(
+                        mPaddingStart, sourceDensity, targetDensity, false);
             }
             if (mPaddingEnd > 0) {
-                mPaddingEnd = Bitmap.scaleFromDensity(
-                        mPaddingEnd, sourceDensity, targetDensity);
+                mPaddingEnd = Drawable.scaleFromDensity(
+                        mPaddingEnd, sourceDensity, targetDensity, false);
             }
         }
 
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 231405d..4d51d63 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -181,10 +181,10 @@
     }
 
     private static Insets scaleFromDensity(Insets insets, int sdensity, int tdensity) {
-        int left = Bitmap.scaleFromDensity(insets.left, sdensity, tdensity);
-        int top = Bitmap.scaleFromDensity(insets.top, sdensity, tdensity);
-        int right = Bitmap.scaleFromDensity(insets.right, sdensity, tdensity);
-        int bottom = Bitmap.scaleFromDensity(insets.bottom, sdensity, tdensity);
+        int left = Drawable.scaleFromDensity(insets.left, sdensity, tdensity, true);
+        int top = Drawable.scaleFromDensity(insets.top, sdensity, tdensity, true);
+        int right = Drawable.scaleFromDensity(insets.right, sdensity, tdensity, true);
+        int bottom = Drawable.scaleFromDensity(insets.bottom, sdensity, tdensity, true);
         return Insets.of(left, top, right, bottom);
     }
 
@@ -196,18 +196,20 @@
             mBitmapHeight = mNinePatch.getHeight();
             mOpticalInsets = mNinePatchState.mOpticalInsets;
         } else {
-            mBitmapWidth = Bitmap.scaleFromDensity(mNinePatch.getWidth(), sdensity, tdensity);
-            mBitmapHeight = Bitmap.scaleFromDensity(mNinePatch.getHeight(), sdensity, tdensity);
+            mBitmapWidth = Drawable.scaleFromDensity(
+                    mNinePatch.getWidth(), sdensity, tdensity, true);
+            mBitmapHeight = Drawable.scaleFromDensity(
+                    mNinePatch.getHeight(), sdensity, tdensity, true);
             if (mNinePatchState.mPadding != null && mPadding != null) {
                 Rect dest = mPadding;
                 Rect src = mNinePatchState.mPadding;
                 if (dest == src) {
                     mPadding = dest = new Rect(src);
                 }
-                dest.left = Bitmap.scaleFromDensity(src.left, sdensity, tdensity);
-                dest.top = Bitmap.scaleFromDensity(src.top, sdensity, tdensity);
-                dest.right = Bitmap.scaleFromDensity(src.right, sdensity, tdensity);
-                dest.bottom = Bitmap.scaleFromDensity(src.bottom, sdensity, tdensity);
+                dest.left = Drawable.scaleFromDensity(src.left, sdensity, tdensity, true);
+                dest.top = Drawable.scaleFromDensity(src.top, sdensity, tdensity, true);
+                dest.right = Drawable.scaleFromDensity(src.right, sdensity, tdensity, true);
+                dest.bottom = Drawable.scaleFromDensity(src.bottom, sdensity, tdensity, true);
             }
             mOpticalInsets = scaleFromDensity(mNinePatchState.mOpticalInsets, sdensity, tdensity);
         }
@@ -490,8 +492,7 @@
             state.mTint = tint;
         }
 
-        final int densityDpi = r.getDisplayMetrics().densityDpi;
-        state.mTargetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+        state.mTargetDensity = Drawable.resolveDensity(r, state.mTargetDensity);
     }
 
     @Override
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index a196fad..52e7f24 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -1008,7 +1008,8 @@
 
         private void applyDensityScaling(int sourceDensity, int targetDensity) {
             if (mMaxRadius != RADIUS_AUTO) {
-                mMaxRadius = Bitmap.scaleFromDensity(mMaxRadius, sourceDensity, targetDensity);
+                mMaxRadius = Drawable.scaleFromDensity(
+                        mMaxRadius, sourceDensity, targetDensity, true);
             }
         }
 
@@ -1039,16 +1040,13 @@
     private RippleDrawable(RippleState state, Resources res) {
         mState = new RippleState(state, this, res);
         mLayerState = mState;
+        mDensity = Drawable.resolveDensity(res, mState.mDensity);
 
         if (mState.mNum > 0) {
             ensurePadding();
             refreshPadding();
         }
 
-        if (res != null) {
-            mDensity = res.getDisplayMetrics().density;
-        }
-
         updateLocalState();
     }
 
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index e161f3d..eee9b24 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -259,13 +259,7 @@
      *            displayed, or {@code null} to use the constant state defaults
      */
     private void updateLocalState(Resources res) {
-        if (res != null) {
-            final int densityDpi = res.getDisplayMetrics().densityDpi;
-            mTargetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
-        } else {
-            mTargetDensity = mVectorState.mVPathRenderer.mSourceDensity;
-        }
-
+        mTargetDensity = Drawable.resolveDensity(res, mVectorState.mVPathRenderer.mSourceDensity);
         mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode);
         computeVectorSize();
     }
@@ -453,18 +447,18 @@
         final int sourceDensity = pathRenderer.mSourceDensity;
         final int targetDensity = mTargetDensity;
         if (targetDensity != sourceDensity) {
-            mDpiScaledWidth = Bitmap.scaleFromDensity(
-                    (int) pathRenderer.mBaseWidth, sourceDensity, targetDensity);
-            mDpiScaledHeight = Bitmap.scaleFromDensity(
-                    (int) pathRenderer.mBaseHeight,sourceDensity, targetDensity);
-            final int left = Bitmap.scaleFromDensity(
-                    opticalInsets.left, sourceDensity, targetDensity);
-            final int right = Bitmap.scaleFromDensity(
-                    opticalInsets.right, sourceDensity, targetDensity);
-            final int top = Bitmap.scaleFromDensity(
-                    opticalInsets.top, sourceDensity, targetDensity);
-            final int bottom = Bitmap.scaleFromDensity(
-                    opticalInsets.bottom, sourceDensity, targetDensity);
+            mDpiScaledWidth = Drawable.scaleFromDensity(
+                    (int) pathRenderer.mBaseWidth, sourceDensity, targetDensity, true);
+            mDpiScaledHeight = Drawable.scaleFromDensity(
+                    (int) pathRenderer.mBaseHeight,sourceDensity, targetDensity, true);
+            final int left = Drawable.scaleFromDensity(
+                    opticalInsets.left, sourceDensity, targetDensity, false);
+            final int right = Drawable.scaleFromDensity(
+                    opticalInsets.right, sourceDensity, targetDensity, false);
+            final int top = Drawable.scaleFromDensity(
+                    opticalInsets.top, sourceDensity, targetDensity, false);
+            final int bottom = Drawable.scaleFromDensity(
+                    opticalInsets.bottom, sourceDensity, targetDensity, false);
             mDpiScaledInsets = Insets.of(left, top, right, bottom);
         } else {
             mDpiScaledWidth = (int) pathRenderer.mBaseWidth;
@@ -600,11 +594,10 @@
 
         // The density may have changed since the last update (if any). Any
         // dimension-type attributes will need their default values scaled.
-        final int densityDpi = a.getResources().getDisplayMetrics().densityDpi;
-        final int newSourceDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
-        final int oldSourceDensity = pathRenderer.mSourceDensity;
-        final float densityScale = newSourceDensity / (float) oldSourceDensity;
-        pathRenderer.mSourceDensity = newSourceDensity;
+        final int targetDensity = Drawable.resolveDensity(a.getResources(), 0);
+        final int sourceDensity = pathRenderer.mSourceDensity;
+        final float densityScale = targetDensity / (float) sourceDensity;
+        pathRenderer.mSourceDensity = targetDensity;
 
         final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
         if (tintMode != -1) {
@@ -647,16 +640,16 @@
                     "<vector> tag requires height > 0");
         }
 
-        final int insetLeft = a.getDimensionPixelSize(
+        final int insetLeft = a.getDimensionPixelOffset(
                 R.styleable.VectorDrawable_opticalInsetLeft,
                 (int) (pathRenderer.mOpticalInsets.left * densityScale));
-        final int insetTop = a.getDimensionPixelSize(
+        final int insetTop = a.getDimensionPixelOffset(
                 R.styleable.VectorDrawable_opticalInsetTop,
                 (int) (pathRenderer.mOpticalInsets.top * densityScale));
-        final int insetRight = a.getDimensionPixelSize(
+        final int insetRight = a.getDimensionPixelOffset(
                 R.styleable.VectorDrawable_opticalInsetRight,
                 (int) (pathRenderer.mOpticalInsets.right * densityScale));
-        final int insetBottom = a.getDimensionPixelSize(
+        final int insetBottom = a.getDimensionPixelOffset(
                 R.styleable.VectorDrawable_opticalInsetBottom,
                 (int) (pathRenderer.mOpticalInsets.bottom * densityScale));
         pathRenderer.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index d94c91d..ae5fa6c 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -56,6 +56,7 @@
     Layer.cpp \
     LayerCache.cpp \
     LayerRenderer.cpp \
+    LayerUpdateQueue.cpp \
     Matrix.cpp \
     OpenGLRenderer.cpp \
     Patch.cpp \
@@ -204,6 +205,7 @@
     unit_tests/ClipAreaTests.cpp \
     unit_tests/DamageAccumulatorTests.cpp \
     unit_tests/FatVectorTests.cpp \
+    unit_tests/LayerUpdateQueueTests.cpp \
     unit_tests/LinearAllocatorTests.cpp \
     unit_tests/StringUtilsTests.cpp
 
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 0868853..2fca5ea 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -20,6 +20,7 @@
 #include "Glop.h"
 #include "GlopBuilder.h"
 #include "renderstate/RenderState.h"
+#include "utils/FatVector.h"
 #include "utils/GLUtils.h"
 
 namespace android {
@@ -29,10 +30,13 @@
 // OffscreenBuffer
 ////////////////////////////////////////////////////////////////////////////////
 
-OffscreenBuffer::OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight,
+OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
+        uint32_t textureWidth, uint32_t textureHeight,
         uint32_t viewportWidth, uint32_t viewportHeight)
-        : texture(caches)
-        , texCoords(0, viewportHeight / float(textureHeight), viewportWidth / float(textureWidth), 0) {
+        : renderState(renderState)
+        , viewportWidth(viewportWidth)
+        , viewportHeight(viewportHeight)
+        , texture(caches) {
     texture.width = textureWidth;
     texture.height = textureHeight;
 
@@ -48,16 +52,72 @@
             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::startLayer(uint32_t width, uint32_t height) {
+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) {
     LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
 
-    // TODO: really should be caching these!
-    OffscreenBuffer* buffer = new OffscreenBuffer(mCaches, width, height, width, height);
-    mRenderTarget.offscreenBuffer = buffer;
+    OffscreenBuffer* buffer = createOffscreenBuffer(mRenderState, width, height);
+    startLayer(buffer);
+    return buffer;
+}
+
+void BakedOpRenderer::startLayer(OffscreenBuffer* offscreenBuffer) {
+    mRenderTarget.offscreenBuffer = offscreenBuffer;
 
     // create and bind framebuffer
     mRenderTarget.frameBufferId = mRenderState.genFramebuffer();
@@ -65,7 +125,7 @@
 
     // attach the texture to the FBO
     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
-            buffer->texture.id, 0);
+            offscreenBuffer->texture.id, 0);
     LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED");
     LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
             "framebuffer incomplete!");
@@ -75,11 +135,11 @@
     glClear(GL_COLOR_BUFFER_BIT);
 
     // Change the viewport & ortho projection
-    setViewport(width, height);
-    return buffer;
+    setViewport(offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight);
 }
 
 void BakedOpRenderer::endLayer() {
+    mRenderTarget.offscreenBuffer->updateMeshFromRegion();
     mRenderTarget.offscreenBuffer = nullptr;
 
     // Detach the texture from the FBO
@@ -144,6 +204,12 @@
         mRenderState.scissor().set(clip.left, mRenderTarget.viewportHeight - clip.bottom,
             clip.getWidth(), clip.getHeight());
     }
+    if (mRenderTarget.offscreenBuffer) { // TODO: not with multi-draw
+        // register layer damage to draw-back region
+        const Rect& uiDirty = state.computedState.clippedBounds;
+        android::Rect dirty(uiDirty.left, uiDirty.top, uiDirty.right, uiDirty.bottom);
+        mRenderTarget.offscreenBuffer->region.orSelf(dirty);
+    }
     mRenderState.render(glop, mRenderTarget.orthoMatrix);
     mHasDrawn = true;
 }
@@ -156,6 +222,14 @@
     LOG_ALWAYS_FATAL("unsupported operation");
 }
 
+void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) {
+    LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) {
+    LOG_ALWAYS_FATAL("unsupported operation");
+}
+
 void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) {
     renderer.caches().textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
     Texture* texture = renderer.getTexture(op.bitmap);
@@ -199,36 +273,26 @@
     renderer.renderGlop(state, glop);
 }
 
-void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) {
-    LOG_ALWAYS_FATAL("unsupported operation");
-}
-
-void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) {
-    LOG_ALWAYS_FATAL("unsupported operation");
-}
-
 void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
     OffscreenBuffer* buffer = *op.layerHandle;
 
     // TODO: extend this to handle HW layers & paint properties which
     // reside in node.properties().layerProperties()
-    float layerAlpha = (op.paint->getAlpha() / 255.0f) * state.alpha;
-    const bool tryToSnap = state.computedState.transform.isPureTranslate();
+    float layerAlpha = op.alpha * state.alpha;
     Glop glop;
     GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
             .setRoundRectClipState(state.roundRectClipState)
-            .setMeshTexturedUvQuad(nullptr, buffer->texCoords)
-            .setFillLayer(buffer->texture, op.paint->getColorFilter(), layerAlpha, PaintUtils::getXfermodeDirect(op.paint), Blend::ModeOrderSwap::NoSwap)
+            .setMeshTexturedIndexedVbo(buffer->vbo, buffer->elementCount)
+            .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap)
             .setTransform(state.computedState.transform, TransformFlags::None)
-            .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds)
+            .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top,
+                    Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
             .build();
     renderer.renderGlop(state, glop);
 
-    // destroy and delete, since each clipped saveLayer is only drawn once.
-    buffer->texture.deleteTexture();
-
-    // TODO: return texture/offscreenbuffer to cache!
-    delete buffer;
+    if (op.destroy) {
+        BakedOpRenderer::destroyOffscreenBuffer(buffer);
+    }
 }
 
 } // namespace uirenderer
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index 16afad4..aa1e67d 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -35,12 +35,24 @@
  */
 class OffscreenBuffer {
 public:
-    OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight,
+    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;
-    Rect texCoords;
+
+    // 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;
 };
 
 /**
@@ -60,12 +72,17 @@
             , 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* startLayer(uint32_t width, uint32_t height);
+    OffscreenBuffer* createLayer(uint32_t width, uint32_t height);
+    void startLayer(OffscreenBuffer* offscreenBuffer);
     void endLayer();
 
     Texture* getTexture(const SkBitmap* bitmap);
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 7c63e31..94a11f1 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -23,6 +23,7 @@
 #include "ShadowTessellator.h"
 #include "utils/GLUtils.h"
 
+#include <cutils/properties.h>
 #include <utils/Log.h>
 #include <utils/String8.h>
 
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 61e958d..330dc29 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -43,7 +43,6 @@
 #include <GLES3/gl3.h>
 
 #include <utils/KeyedVector.h>
-#include <utils/Singleton.h>
 
 #include <cutils/compiler.h>
 
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 86796c5..00c4e2d 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -154,7 +154,11 @@
         return allocator.usedSize();
     }
     bool isEmpty() {
+#if HWUI_NEW_OPS
+        return ops.empty();
+#else
         return !hasDrawOps;
+#endif
     }
 
 private:
@@ -179,7 +183,7 @@
     // List of functors
     LsaVector<Functor*> functors;
 
-    bool hasDrawOps;
+    bool hasDrawOps; // only used if !HWUI_NEW_OPS
 
     void cleanupResources();
 };
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 06c8a21..6dd29ad 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -20,6 +20,7 @@
 #include "Properties.h"
 #include "utils/StringUtils.h"
 
+#include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 #include <utils/Log.h>
 
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 0a30d16..6689b88 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -19,12 +19,6 @@
 
 #include <cutils/compiler.h>
 
-#include <utils/Singleton.h>
-#include <utils/SortedVector.h>
-#include <utils/String8.h>
-
-#include <GLES2/gl2.h>
-
 namespace android {
 namespace uirenderer {
 
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index d2da851..f3ac93b 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -70,6 +70,20 @@
 // Mesh
 ////////////////////////////////////////////////////////////////////////////////
 
+GlopBuilder& GlopBuilder::setMeshTexturedIndexedVbo(GLuint vbo, GLsizei elementCount) {
+    TRIGGER_STAGE(kMeshStage);
+
+    mOutGlop->mesh.primitiveMode = GL_TRIANGLES;
+    mOutGlop->mesh.indices = { mRenderState.meshState().getQuadListIBO(), nullptr };
+    mOutGlop->mesh.vertices = {
+            vbo,
+            VertexAttribFlags::TextureCoord,
+            nullptr, nullptr, nullptr,
+            kTextureVertexStride };
+    mOutGlop->mesh.elementCount = elementCount;
+    return *this;
+}
+
 GlopBuilder& GlopBuilder::setMeshUnitQuad() {
     TRIGGER_STAGE(kMeshStage);
 
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index 6f5802e..6270dcb 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -47,6 +47,7 @@
 public:
     GlopBuilder(RenderState& renderState, Caches& caches, Glop* outGlop);
 
+    GlopBuilder& setMeshTexturedIndexedVbo(GLuint vbo, GLsizei elementCount);
     GlopBuilder& setMeshUnitQuad();
     GlopBuilder& setMeshTexturedUnitQuad(const UvMapper* uvMapper);
     GlopBuilder& setMeshTexturedUvQuad(const UvMapper* uvMapper, const Rect uvs);
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index aa105f9..8c46450 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -21,6 +21,8 @@
 #include "GradientCache.h"
 #include "Properties.h"
 
+#include <cutils/properties.h>
+
 namespace android {
 namespace uirenderer {
 
diff --git a/libs/hwui/LayerUpdateQueue.cpp b/libs/hwui/LayerUpdateQueue.cpp
new file mode 100644
index 0000000..db5f676
--- /dev/null
+++ b/libs/hwui/LayerUpdateQueue.cpp
@@ -0,0 +1,42 @@
+/*
+ * 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 "LayerUpdateQueue.h"
+
+#include "RenderNode.h"
+
+namespace android {
+namespace uirenderer {
+
+void LayerUpdateQueue::clear() {
+    mEntries.clear();
+}
+
+void LayerUpdateQueue::enqueueLayerWithDamage(RenderNode* renderNode, Rect damage) {
+    damage.doIntersect(0, 0, renderNode->getWidth(), renderNode->getHeight());
+    if (!damage.isEmpty()) {
+        for (Entry& entry : mEntries) {
+            if (CC_UNLIKELY(entry.renderNode == renderNode)) {
+                entry.damage.unionWith(damage);
+                return;
+            }
+        }
+        mEntries.emplace_back(renderNode, damage);
+    }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/LayerUpdateQueue.h b/libs/hwui/LayerUpdateQueue.h
new file mode 100644
index 0000000..be612d2
--- /dev/null
+++ b/libs/hwui/LayerUpdateQueue.h
@@ -0,0 +1,53 @@
+/*
+ * 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_LAYER_UPDATE_QUEUE_H
+#define ANDROID_HWUI_LAYER_UPDATE_QUEUE_H
+
+#include "Rect.h"
+#include "utils/Macros.h"
+
+#include <vector>
+#include <unordered_map>
+
+namespace android {
+namespace uirenderer {
+
+class RenderNode;
+
+class LayerUpdateQueue {
+    PREVENT_COPY_AND_ASSIGN(LayerUpdateQueue);
+public:
+    struct Entry {
+        Entry(RenderNode* renderNode, const Rect& damage)
+                : renderNode(renderNode)
+                , damage(damage) {}
+        RenderNode* renderNode;
+        Rect damage;
+    };
+
+    LayerUpdateQueue() {}
+    void enqueueLayerWithDamage(RenderNode* renderNode, Rect dirty);
+    void clear();
+    const std::vector<Entry> entries() const { return mEntries; }
+private:
+    std::vector<Entry> mEntries;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_LAYER_UPDATE_QUEUE_H
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index ddeb336..163f7cc 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -18,6 +18,7 @@
 
 #include "utils/PaintUtils.h"
 #include "RenderNode.h"
+#include "LayerUpdateQueue.h"
 
 #include "SkCanvas.h"
 #include "utils/Trace.h"
@@ -202,6 +203,14 @@
     Rect mClipRect;
 };
 
+OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height,
+        const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
+        : width(width)
+        , height(height)
+        , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
+        , beginLayerOp(beginLayerOp)
+        , renderNode(renderNode) {}
+
 // iterate back toward target to see if anything drawn since should overlap the new op
 // if no target, merging ops still iterate to find similar batch to insert after
 void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
@@ -288,33 +297,48 @@
 }
 
 void OpReorderer::LayerReorderer::dump() const {
+    ALOGD("LayerReorderer %p, %ux%u buffer %p, blo %p, rn %p",
+            this, width, height, offscreenBuffer, beginLayerOp, renderNode);
     for (const BatchBase* batch : mBatches) {
         batch->dump();
     }
 }
 
-OpReorderer::OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
+OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
+        uint32_t viewportWidth, uint32_t viewportHeight,
         const std::vector< sp<RenderNode> >& nodes)
         : 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,
             Vector3());
+
+    // Render all layers to be updated, in order. Defer in reverse order, so that they'll be
+    // updated in the order they're passed in (mLayerReorderers are issued to Renderer in reverse)
+    for (int i = layers.entries().size() - 1; i >= 0; i--) {
+        RenderNode* layerNode = layers.entries()[i].renderNode;
+        const Rect& layerDamage = layers.entries()[i].damage;
+
+        saveForLayer(layerNode->getWidth(), layerNode->getHeight(), nullptr, layerNode);
+        mCanvasState.writableSnapshot()->setClip(
+                layerDamage.left, layerDamage.top, layerDamage.right, layerDamage.bottom);
+
+        if (layerNode->getDisplayList()) {
+            deferImpl(*(layerNode->getDisplayList()));
+        }
+        restoreForLayer();
+    }
+
+    // Defer Fbo0
     for (const sp<RenderNode>& node : nodes) {
         if (node->nothingToDraw()) continue;
 
-        // TODO: dedupe this code with onRenderNode()
-        mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
-        if (node->applyViewProperties(mCanvasState)) {
-            // not rejected do ops...
-            const DisplayList& displayList = node->getDisplayList();
-            deferImpl(displayList);
-        }
-        mCanvasState.restore();
+        int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+        deferNodePropsAndOps(*node);
+        mCanvasState.restoreToCount(count);
     }
 }
 
@@ -334,6 +358,23 @@
 
 void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
 
+void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
+    if (node.applyViewProperties(mCanvasState)) {
+        // not rejected so render
+        if (node.getLayer()) {
+            // HW layer
+            LayerOp* drawLayerOp = new (mAllocator) LayerOp(node);
+            BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
+            if (bakedOpState) {
+                // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack)
+                currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
+            }
+        } else {
+            deferImpl(*(node.getDisplayList()));
+        }
+    }
+}
+
 /**
  * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
  *
@@ -365,11 +406,9 @@
     mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top,
             op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op);
 
-    // apply RenderProperties state
-    if (op.renderNode->applyViewProperties(mCanvasState)) {
-        // if node not rejected based on properties, do ops...
-        deferImpl(op.renderNode->getDisplayList());
-    }
+    // then apply state from node properties, and defer ops
+    deferNodePropsAndOps(*op.renderNode);
+
     mCanvasState.restoreToCount(count);
 }
 
@@ -400,10 +439,8 @@
     currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
 }
 
-// TODO: test rejection at defer time, where the bounds become empty
-void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
-    const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
-    const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
+void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+        const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
 
     mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
     mCanvasState.writableSnapshot()->transform->loadIdentity();
@@ -412,18 +449,27 @@
 
     // create a new layer, and push its index on the stack
     mLayerStack.push_back(mLayerReorderers.size());
-    mLayerReorderers.emplace_back(layerWidth, layerHeight);
-    mLayerReorderers.back().beginLayerOp = &op;
+    mLayerReorderers.emplace_back(layerWidth, layerHeight, beginLayerOp, renderNode);
+}
+
+void OpReorderer::restoreForLayer() {
+    // restore canvas, and pop finished layer off of the stack
+    mCanvasState.restore();
+    mLayerStack.pop_back();
+}
+
+// TODO: test rejection at defer time, where the bounds become empty
+void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
+    const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
+    const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
+    saveForLayer(layerWidth, layerHeight, &op, nullptr);
 }
 
 void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
-    mCanvasState.restore();
-
     const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
-
-    // pop finished layer off of the stack
     int finishedLayerIndex = mLayerStack.back();
-    mLayerStack.pop_back();
+
+    restoreForLayer();
 
     // record the draw operation into the previous layer's list of draw commands
     // uses state from the associated beginLayerOp, since it has all the state needed for drawing
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 927ecfa..77be402 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -32,6 +32,7 @@
 
 class BakedOpState;
 class BatchBase;
+class LayerUpdateQueue;
 class MergingOpBatch;
 class OffscreenBuffer;
 class OpBatch;
@@ -64,9 +65,14 @@
      */
     class LayerReorderer {
     public:
+        // Create LayerReorderer for Fbo0
         LayerReorderer(uint32_t width, uint32_t height)
-                : width(width)
-                , height(height) {}
+                : LayerReorderer(width, height, nullptr, nullptr) {};
+
+        // Create LayerReorderer for an offscreen layer, where beginLayerOp is present for a
+        // saveLayer, renderNode is present for a HW layer.
+        LayerReorderer(uint32_t width, uint32_t height,
+                const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
 
         // iterate back toward target to see if anything drawn since should overlap the new op
         // if no target, merging ops still iterate to find similar batch to insert after
@@ -92,12 +98,12 @@
 
         void dump() const;
 
-        OffscreenBuffer* offscreenBuffer = nullptr;
-        const BeginLayerOp* beginLayerOp = nullptr;
         const uint32_t width;
         const uint32_t height;
+        OffscreenBuffer* offscreenBuffer;
+        const BeginLayerOp* beginLayerOp;
+        const RenderNode* renderNode;
     private:
-
         std::vector<BatchBase*> mBatches;
 
         /**
@@ -112,8 +118,8 @@
 
     };
 public:
-    // TODO: not final, just presented this way for simplicity. Layers too?
-    OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
+    OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
+            uint32_t viewportWidth, uint32_t viewportHeight,
             const std::vector< sp<RenderNode> >& nodes);
 
     OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
@@ -144,8 +150,13 @@
         // later in the list will be drawn by earlier ones
         for (int i = mLayerReorderers.size() - 1; i >= 1; i--) {
             LayerReorderer& layer = mLayerReorderers[i];
-            if (!layer.empty()) {
-                layer.offscreenBuffer = renderer.startLayer(layer.width, layer.height);
+            if (layer.renderNode) {
+                // cached HW layer - can't skip layer if empty
+                renderer.startLayer(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.replayBakedOpsImpl((void*)&renderer, receivers);
                 renderer.endLayer();
             }
@@ -171,12 +182,19 @@
     virtual GLuint getTargetFbo() const override { return 0; }
 
 private:
+    void saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+            const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+    void restoreForLayer();
+
     LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; }
 
     BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) {
         return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
     }
 
+    // should always be surrounded by a save/restore pair
+    void deferNodePropsAndOps(RenderNode& node);
+
     void deferImpl(const DisplayList& displayList);
 
     void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 4031f2e1..fd9ab18 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -30,6 +30,8 @@
 #include "thread/Signal.h"
 #include "thread/TaskProcessor.h"
 
+#include <cutils/properties.h>
+
 namespace android {
 namespace uirenderer {
 
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index c0c61db..e818186 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -17,8 +17,12 @@
 
 #include "Debug.h"
 
-#include <algorithm>
+#include <cutils/compiler.h>
 #include <cutils/log.h>
+#include <cutils/properties.h>
+
+#include <algorithm>
+#include <cstdlib>
 
 namespace android {
 namespace uirenderer {
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 74cd74b..1293c78 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -18,8 +18,6 @@
 #define ANDROID_HWUI_PROPERTIES_H
 
 #include <cutils/properties.h>
-#include <stdlib.h>
-#include <utils/Singleton.h>
 
 /**
  * This file contains the list of system properties used to configure
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index 7874d85..9ae868a 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -20,6 +20,7 @@
 #include "utils/LinearAllocator.h"
 #include "Rect.h"
 #include "Matrix.h"
+#include "RenderNode.h"
 
 #include "SkXfermode.h"
 
@@ -136,13 +137,42 @@
             : RecordedOp(RecordedOpId::EndLayerOp, Rect(0, 0), Matrix4::identity(), Rect(0, 0), nullptr) {}
 };
 
+/**
+ * Draws an OffscreenBuffer.
+ *
+ * Alpha, mode, and colorfilter are embedded, since LayerOps are always dynamically generated,
+ * when creating/tracking a SkPaint* during defer isn't worth the bother.
+ */
 struct LayerOp : RecordedOp {
+    // Records a one-use (saveLayer) layer for drawing. Once drawn, the layer will be destroyed.
     LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle)
-            : SUPER(LayerOp)
-            , layerHandle(layerHandle) {}
+            : SUPER_PAINTLESS(LayerOp)
+            , layerHandle(layerHandle)
+            , alpha(paint->getAlpha() / 255.0f)
+            , mode(PaintUtils::getXfermodeDirect(paint))
+            , colorFilter(paint->getColorFilter())
+            , destroy(true) {}
+
+    LayerOp(RenderNode& node)
+        : RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), Rect(node.getWidth(), node.getHeight()), nullptr)
+        , layerHandle(node.getLayerHandle())
+        , alpha(node.properties().layerProperties().alpha() / 255.0f)
+        , mode(node.properties().layerProperties().xferMode())
+        , colorFilter(node.properties().layerProperties().colorFilter())
+        , destroy(false) {}
+
     // Records a handle to the Layer object, since the Layer itself won't be
     // constructed until after this operation is constructed.
     OffscreenBuffer** layerHandle;
+    const float alpha;
+    const SkXfermode::Mode mode;
+
+    // pointer to object owned by either LayerProperties, or a recorded Paint object in a
+    // BeginLayerOp. Lives longer than LayerOp in either case, so no skia ref counting is used.
+    SkColorFilter* colorFilter;
+
+    // whether to destroy the layer, once rendered
+    const bool destroy;
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 273af3a..7c460b1 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -77,7 +77,6 @@
 // ----------------------------------------------------------------------------
 
 void RecordingCanvas::onViewportInitialized() {
-
 }
 
 void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 454ee24..8a56475 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -26,9 +26,10 @@
 #include "SkiaCanvasProxy.h"
 #include "Snapshot.h"
 
-#include "SkDrawFilter.h"
-#include "SkPaint.h"
-#include "SkTLazy.h"
+#include <SkDrawFilter.h>
+#include <SkPaint.h>
+#include <SkTLazy.h>
+
 #include <vector>
 
 namespace android {
diff --git a/libs/hwui/RenderBufferCache.cpp b/libs/hwui/RenderBufferCache.cpp
index 8beed25..11d7a6a 100644
--- a/libs/hwui/RenderBufferCache.cpp
+++ b/libs/hwui/RenderBufferCache.cpp
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-#include <utils/Log.h>
-
 #include "Debug.h"
 #include "Properties.h"
 #include "RenderBufferCache.h"
 
+#include <utils/Log.h>
+
+#include <cstdlib>
+
 namespace android {
 namespace uirenderer {
 
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 39cb8e9..0601944 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -20,6 +20,7 @@
 #include "Debug.h"
 #if HWUI_NEW_OPS
 #include "RecordedOp.h"
+#include "BakedOpRenderer.h"
 #endif
 #include "DisplayListOp.h"
 #include "LayerRenderer.h"
@@ -42,11 +43,15 @@
 namespace uirenderer {
 
 void RenderNode::debugDumpLayers(const char* prefix) {
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("TODO: dump layer");
+#else
     if (mLayer) {
         ALOGD("%sNode %p (%s) has layer %p (fbo = %u, wasBuildLayered = %s)",
                 prefix, this, getName(), mLayer, mLayer->getFbo(),
                 mLayer->wasBuildLayered ? "true" : "false");
     }
+#endif
     if (mDisplayList) {
         for (auto&& child : mDisplayList->getChildren()) {
             child->renderNode->debugDumpLayers(prefix);
@@ -60,18 +65,21 @@
         , mDisplayList(nullptr)
         , mStagingDisplayList(nullptr)
         , mAnimatorManager(*this)
-        , mLayer(nullptr)
         , mParentCount(0) {
 }
 
 RenderNode::~RenderNode() {
     deleteDisplayList();
     delete mStagingDisplayList;
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL_IF(mLayer, "layer missed detachment!");
+#else
     if (mLayer) {
         ALOGW("Memory Warning: Layer %p missed its detachment, held on to for far too long!", mLayer);
         mLayer->postDecStrong();
         mLayer = nullptr;
     }
+#endif
 }
 
 void RenderNode::setStagingDisplayList(DisplayList* displayList) {
@@ -240,13 +248,29 @@
     }
 }
 
+layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) {
+#if HWUI_NEW_OPS
+    return BakedOpRenderer::createOffscreenBuffer(renderState, width, height);
+#else
+    return LayerRenderer::createRenderLayer(renderState, width, height);
+#endif
+}
+
+void destroyLayer(layer_t* layer) {
+#if HWUI_NEW_OPS
+    BakedOpRenderer::destroyOffscreenBuffer(layer);
+#else
+    LayerRenderer::destroyLayer(layer);
+#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)
     // we need to destroy any Layers we may have had previously
     if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable())) {
         if (CC_UNLIKELY(mLayer)) {
-            LayerRenderer::destroyLayer(mLayer);
+            destroyLayer(mLayer);
             mLayer = nullptr;
         }
         return;
@@ -254,14 +278,18 @@
 
     bool transformUpdateNeeded = false;
     if (!mLayer) {
-        mLayer = LayerRenderer::createRenderLayer(
-                info.canvasContext.getRenderState(), getWidth(), getHeight());
-        applyLayerPropertiesToLayer(info);
-        damageSelf(info);
-        transformUpdateNeeded = true;
+            mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
+            damageSelf(info);
+            transformUpdateNeeded = true;
+#if HWUI_NEW_OPS
+    } else if (mLayer->viewportWidth != getWidth() || mLayer->viewportHeight != getHeight()) {
+        // TODO: allow it to grow larger
+        if (getWidth() > mLayer->texture.width || getHeight() > mLayer->texture.height) {
+#else
     } else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) {
         if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) {
-            LayerRenderer::destroyLayer(mLayer);
+#endif
+            destroyLayer(mLayer);
             mLayer = nullptr;
         }
         damageSelf(info);
@@ -276,7 +304,7 @@
         if (info.errorHandler) {
             std::ostringstream err;
             err << "Unable to create layer for " << getName();
-            const int maxTextureSize = Caches::getInstance().maxTextureSize;
+            const uint32_t  maxTextureSize = Caches::getInstance().maxTextureSize;
             if (getWidth() > maxTextureSize || getHeight() > maxTextureSize) {
                 err << ", size " << getWidth() << "x" << getHeight()
                         << " exceeds max size " << maxTextureSize;
@@ -292,9 +320,16 @@
         // update the transform in window of the layer to reset its origin wrt light source position
         Matrix4 windowTransform;
         info.damageAccumulator->computeCurrentTransform(&windowTransform);
+#if HWUI_NEW_OPS
+        // TODO: update layer transform (perhaps as part of enqueueLayerWithDamage)
+#else
         mLayer->setWindowTransform(windowTransform);
+#endif
     }
 
+#if HWUI_NEW_OPS
+    info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty);
+#else
     if (dirty.intersect(0, 0, getWidth(), getHeight())) {
         dirty.roundOut(&dirty);
         mLayer->updateDeferred(this, dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom);
@@ -304,6 +339,7 @@
     if (info.renderer && mLayer->deferredUpdateScheduled) {
         info.renderer->pushLayerUpdate(mLayer);
     }
+#endif
 
     // There might be prefetched layers that need to be accounted for.
     // That might be us, so tell CanvasContext that this layer is in the
@@ -365,7 +401,9 @@
         damageSelf(info);
         info.damageAccumulator->popTransform();
         syncProperties();
+#if !HWUI_NEW_OPS
         applyLayerPropertiesToLayer(info);
+#endif
         // We could try to be clever and only re-damage if the matrix changed.
         // However, we don't need to worry about that. The cost of over-damaging
         // here is only going to be a single additional map rect of this node
@@ -376,6 +414,7 @@
     }
 }
 
+#if !HWUI_NEW_OPS
 void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) {
     if (CC_LIKELY(!mLayer)) return;
 
@@ -384,6 +423,7 @@
     mLayer->setColorFilter(props.colorFilter());
     mLayer->setBlend(props.needsBlending());
 }
+#endif
 
 void RenderNode::syncDisplayList() {
     // Make sure we inc first so that we don't fluctuate between 0 and 1,
@@ -451,7 +491,7 @@
 
 void RenderNode::destroyHardwareResources() {
     if (mLayer) {
-        LayerRenderer::destroyLayer(mLayer);
+        destroyLayer(mLayer);
         mLayer = nullptr;
     }
     if (mDisplayList) {
@@ -978,7 +1018,11 @@
         return;
     }
 
+#if HWUI_NEW_OPS
+    const bool drawLayer = false;
+#else
     const bool drawLayer = (mLayer && (&renderer != mLayer->renderer.get()));
+#endif
     // If we are updating the contents of mLayer, we don't want to apply any of
     // the RenderNode's properties to this issueOperations pass. Those will all
     // be applied when the layer is drawn, aka when this is true.
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 57e41c6..3500cb2 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -44,13 +44,22 @@
 namespace uirenderer {
 
 class CanvasState;
-class DisplayListOp;
 class DisplayListCanvas;
+class DisplayListOp;
 class OpenGLRenderer;
+class OpReorderer;
 class Rect;
-class Layer;
 class SkiaShader;
 
+
+#if HWUI_NEW_OPS
+class OffscreenBuffer;
+typedef OffscreenBuffer layer_t;
+#else
+class Layer;
+typedef Layer layer_t;
+#endif
+
 class ClipRectOp;
 class SaveLayerOp;
 class SaveOp;
@@ -162,11 +171,11 @@
         return mStagingProperties;
     }
 
-    int getWidth() {
+    uint32_t getWidth() {
         return properties().getWidth();
     }
 
-    int getHeight() {
+    uint32_t getHeight() {
         return properties().getHeight();
     }
 
@@ -193,9 +202,13 @@
     }
 
     // Only call if RenderNode has DisplayList...
-    const DisplayList& getDisplayList() const {
-        return *mDisplayList;
+    const DisplayList* getDisplayList() const {
+        return mDisplayList;
     }
+#if HWUI_NEW_OPS
+    OffscreenBuffer* getLayer() const { return mLayer; }
+    OffscreenBuffer** getLayerHandle() { return &mLayer; } // ugh...
+#endif
 
 private:
     typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
@@ -262,7 +275,9 @@
     void pushStagingPropertiesChanges(TreeInfo& info);
     void pushStagingDisplayListChanges(TreeInfo& info);
     void prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree);
+#if !HWUI_NEW_OPS
     void applyLayerPropertiesToLayer(TreeInfo& info);
+#endif
     void prepareLayer(TreeInfo& info, uint32_t dirtyMask);
     void pushLayerUpdate(TreeInfo& info);
     void deleteDisplayList();
@@ -287,7 +302,7 @@
 
     // Owned by RT. Lifecycle is managed by prepareTree(), with the exception
     // being in ~RenderNode() which may happen on any thread.
-    Layer* mLayer;
+    layer_t* mLayer = nullptr;
 
     /**
      * Draw time state - these properties are only set and used during rendering
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 1c31487..be25516 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -16,11 +16,11 @@
 #ifndef TREEINFO_H
 #define TREEINFO_H
 
-#include <string>
+#include "utils/Macros.h"
 
 #include <utils/Timers.h>
 
-#include "utils/Macros.h"
+#include <string>
 
 namespace android {
 namespace uirenderer {
@@ -30,6 +30,7 @@
 }
 
 class DamageAccumulator;
+class LayerUpdateQueue;
 class OpenGLRenderer;
 class RenderState;
 
@@ -75,9 +76,14 @@
 
     // Must not be null during actual usage
     DamageAccumulator* damageAccumulator = nullptr;
+
+#if HWUI_NEW_OPS
+    LayerUpdateQueue* layerUpdateQueue = nullptr;
+#else
     // The renderer that will be drawing the next frame. Use this to push any
     // layer updates or similar. May be NULL.
     OpenGLRenderer* renderer = nullptr;
+#endif
     ErrorHandler* errorHandler = nullptr;
 
     struct Out {
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index 43f170f..7b8d0e5 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -56,7 +56,9 @@
 
 BENCHMARK_NO_ARG(BM_OpReorderer_deferAndRender);
 void BM_OpReorderer_deferAndRender::Run(int iters) {
-    TestUtils::runOnRenderThread([this, iters](RenderState& renderState, Caches& caches) {
+    TestUtils::runOnRenderThread([this, iters](renderthread::RenderThread& thread) {
+        RenderState& renderState = thread.renderState();
+        Caches& caches = Caches::getInstance();
         StartBenchmarkTiming();
         for (int i = 0; i < iters; i++) {
             OpReorderer reorderer(200, 200, *sReorderingDisplayList);
diff --git a/libs/hwui/renderstate/MeshState.cpp b/libs/hwui/renderstate/MeshState.cpp
index 0521f65..03cb5ce 100644
--- a/libs/hwui/renderstate/MeshState.cpp
+++ b/libs/hwui/renderstate/MeshState.cpp
@@ -100,6 +100,24 @@
     return false;
 }
 
+void MeshState::genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size,
+        const void* data, GLenum usage) {
+    if (!*buffer) {
+        glGenBuffers(1, buffer);
+    }
+    bindMeshBuffer(*buffer);
+    glBufferData(GL_ARRAY_BUFFER, size, data, usage);
+}
+
+void MeshState::deleteMeshBuffer(GLuint buffer) {
+    if (buffer == mCurrentBuffer) {
+        // GL defines that deleting the currently bound VBO rebinds to 0 (no VBO).
+        // Reflect this in our cached value.
+        mCurrentBuffer = 0;
+    }
+    glDeleteBuffers(1, &buffer);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Vertices
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/renderstate/MeshState.h b/libs/hwui/renderstate/MeshState.h
index e80f4d0..6c0fb78 100644
--- a/libs/hwui/renderstate/MeshState.h
+++ b/libs/hwui/renderstate/MeshState.h
@@ -75,6 +75,9 @@
      */
     bool unbindMeshBuffer();
 
+    void genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size, const void* data, GLenum usage);
+    void deleteMeshBuffer(GLuint);
+
     ///////////////////////////////////////////////////////////////////////////////
     // Vertices
     ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index c1f6670..b6fecb4 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -20,6 +20,7 @@
 #include "Caches.h"
 #include "DeferredLayerUpdater.h"
 #include "EglManager.h"
+#include "LayerUpdateQueue.h"
 #include "LayerRenderer.h"
 #include "OpenGLRenderer.h"
 #include "Properties.h"
@@ -198,7 +199,11 @@
     mCurrentFrameInfo->markSyncStart();
 
     info.damageAccumulator = &mDamageAccumulator;
+#if HWUI_NEW_OPS
+    info.layerUpdateQueue = &mLayerUpdateQueue;
+#else
     info.renderer = mCanvas;
+#endif
 
     mAnimationContext->startFrame(info.mode);
     for (const sp<RenderNode>& node : mRenderNodes) {
@@ -333,7 +338,8 @@
     mEglManager.damageFrame(frame, dirty);
 
 #if HWUI_NEW_OPS
-    OpReorderer reorderer(dirty, frame.width(), frame.height(), mRenderNodes);
+    OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), mRenderNodes);
+    mLayerUpdateQueue.clear();
     BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), mOpaque);
     // TODO: profiler().draw(mCanvas);
     reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
@@ -555,7 +561,11 @@
 
     TreeInfo info(TreeInfo::MODE_FULL, *this);
     info.damageAccumulator = &mDamageAccumulator;
+#if HWUI_NEW_OPS
+    info.layerUpdateQueue = &mLayerUpdateQueue;
+#else
     info.renderer = mCanvas;
+#endif
     info.runAnimations = false;
     node->prepareTree(info);
     SkRect ignore;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 30e6562..d656014 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -18,9 +18,10 @@
 #define CANVASCONTEXT_H_
 
 #include "DamageAccumulator.h"
-#include "IContextFactory.h"
 #include "FrameInfo.h"
 #include "FrameInfoVisualizer.h"
+#include "IContextFactory.h"
+#include "LayerUpdateQueue.h"
 #include "RenderNode.h"
 #include "utils/RingBuffer.h"
 #include "renderthread/RenderTask.h"
@@ -83,7 +84,7 @@
     void draw();
     void destroy();
 
-    // IFrameCallback, Chroreographer-driven frame callback entry point
+    // IFrameCallback, Choreographer-driven frame callback entry point
     virtual void doFrame() override;
     void prepareAndDraw(RenderNode* node);
 
@@ -118,7 +119,7 @@
 
     void addRenderNode(RenderNode* node, bool placeFront) {
         int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size());
-        mRenderNodes.emplace( mRenderNodes.begin() + pos, node);
+        mRenderNodes.emplace(mRenderNodes.begin() + pos, node);
     }
 
     void removeRenderNode(RenderNode* node) {
@@ -166,6 +167,7 @@
     OpenGLRenderer* mCanvas = nullptr;
     bool mHaveNewSurface = false;
     DamageAccumulator mDamageAccumulator;
+    LayerUpdateQueue mLayerUpdateQueue;
     std::unique_ptr<AnimationContext> mAnimationContext;
 
     std::vector< sp<RenderNode> > mRenderNodes;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 15ccd6a..a1107f0 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -563,10 +563,7 @@
 void* RenderProxy::postAndWait(MethodInvokeRenderTask* task) {
     void* retval;
     task->setReturnPtr(&retval);
-    SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
-    AutoMutex _lock(mSyncMutex);
-    mRenderThread.queue(&syncTask);
-    mSyncCondition.wait(mSyncMutex);
+    mRenderThread.queueAndWait(task);
     return retval;
 }
 
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 338fab6..d0e601e 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -117,9 +117,6 @@
 
     DrawFrameTask mDrawFrameTask;
 
-    Mutex mSyncMutex;
-    Condition mSyncCondition;
-
     void destroyContext();
 
     void post(RenderTask* task);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 8fcd109..526a848 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -28,9 +28,6 @@
 #include <utils/Log.h>
 
 namespace android {
-using namespace uirenderer::renderthread;
-ANDROID_SINGLETON_STATIC_INSTANCE(RenderThread);
-
 namespace uirenderer {
 namespace renderthread {
 
@@ -136,7 +133,22 @@
     }
 };
 
-RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
+static bool gHasRenderThreadInstance = false;
+
+bool RenderThread::hasInstance() {
+    return gHasRenderThreadInstance;
+}
+
+RenderThread& RenderThread::getInstance() {
+    // This is a pointer because otherwise __cxa_finalize
+    // will try to delete it like a Good Citizen but that causes us to crash
+    // because we don't want to delete the RenderThread normally.
+    static RenderThread* sInstance = new RenderThread();
+    gHasRenderThreadInstance = true;
+    return *sInstance;
+}
+
+RenderThread::RenderThread() : Thread(true)
         , mNextWakeup(LLONG_MAX)
         , mDisplayEventReceiver(nullptr)
         , mVsyncRequested(false)
@@ -313,13 +325,10 @@
 }
 
 void RenderThread::queueAndWait(RenderTask* task) {
-    Mutex mutex;
-    Condition condition;
-    SignalingRenderTask syncTask(task, &mutex, &condition);
-
-    AutoMutex _lock(mutex);
+    SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
+    AutoMutex _lock(mSyncMutex);
     queue(&syncTask);
-    condition.wait(mutex);
+    mSyncCondition.wait(mSyncMutex);
 }
 
 void RenderThread::queueAtFront(RenderTask* task) {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index f3444a8..d8c7e61 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -25,11 +25,11 @@
 #include <cutils/compiler.h>
 #include <ui/DisplayInfo.h>
 #include <utils/Looper.h>
-#include <utils/Mutex.h>
-#include <utils/Singleton.h>
 #include <utils/Thread.h>
 
+#include <condition_variable>
 #include <memory>
+#include <mutex>
 #include <set>
 
 namespace android {
@@ -72,7 +72,7 @@
     ~IFrameCallback() {}
 };
 
-class ANDROID_API RenderThread : public Thread, protected Singleton<RenderThread> {
+class ANDROID_API RenderThread : public Thread {
 public:
     // RenderThread takes complete ownership of tasks that are queued
     // and will delete them after they are run
@@ -100,7 +100,6 @@
     virtual bool threadLoop() override;
 
 private:
-    friend class Singleton<RenderThread>;
     friend class DispatchFrameCallbacks;
     friend class RenderProxy;
     friend class android::uirenderer::TestUtils;
@@ -108,6 +107,9 @@
     RenderThread();
     virtual ~RenderThread();
 
+    static bool hasInstance();
+    static RenderThread& getInstance();
+
     void initThreadLocals();
     void initializeDisplayEventReceiver();
     static int displayEventReceiverCallback(int fd, int events, void* data);
@@ -125,6 +127,8 @@
 
     nsecs_t mNextWakeup;
     TaskQueue mQueue;
+    Mutex mSyncMutex;
+    Condition mSyncCondition;
 
     DisplayInfo mDisplayInfo;
 
diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp
index 2eefd37..29d9803 100644
--- a/libs/hwui/tests/TreeContentAnimation.cpp
+++ b/libs/hwui/tests/TreeContentAnimation.cpp
@@ -24,6 +24,7 @@
 #include <RenderNode.h>
 #include <renderthread/RenderProxy.h>
 #include <renderthread/RenderTask.h>
+#include <unit_tests/TestUtils.h>
 
 #include "Benchmark.h"
 #include "TestContext.h"
@@ -401,3 +402,27 @@
     "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
     TreeContentAnimation::run<SaveLayerAnimation>
 });
+
+
+class HwLayerAnimation : public TreeContentAnimation {
+public:
+    sp<RenderNode> card = TestUtils::createNode<TestCanvas>(0, 0, 200, 200, [] (TestCanvas& canvas) {
+        canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
+    }, true);
+    void createContent(int width, int height, TestCanvas* canvas) override {
+        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
+        canvas->drawRenderNode(card.get());
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        card->mutateStagingProperties().setTranslationX(curFrame);
+        card->mutateStagingProperties().setTranslationY(curFrame);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    }
+};
+static Benchmark _HwLayer(BenchmarkInfo{
+    "hwlayer",
+    "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. "
+    "Tests the hardware layer codepath.",
+    TreeContentAnimation::run<HwLayerAnimation>
+});
diff --git a/libs/hwui/unit_tests/FatVectorTests.cpp b/libs/hwui/unit_tests/FatVectorTests.cpp
index fb760ac5..3ef329a 100644
--- a/libs/hwui/unit_tests/FatVectorTests.cpp
+++ b/libs/hwui/unit_tests/FatVectorTests.cpp
@@ -56,6 +56,27 @@
     }
 }
 
+TEST(FatVector, preSizeConstructor) {
+    {
+        FatVector<int, 4> v(32);
+        EXPECT_EQ(32u, v.capacity());
+        EXPECT_EQ(32u, v.size());
+        EXPECT_FALSE(allocationIsInternal(v));
+    }
+    {
+        FatVector<int, 4> v(4);
+        EXPECT_EQ(4u, v.capacity());
+        EXPECT_EQ(4u, v.size());
+        EXPECT_TRUE(allocationIsInternal(v));
+    }
+    {
+        FatVector<int, 4> v(2);
+        EXPECT_EQ(4u, v.capacity());
+        EXPECT_EQ(2u, v.size());
+        EXPECT_TRUE(allocationIsInternal(v));
+    }
+}
+
 TEST(FatVector, shrink) {
     FatVector<int, 10> v;
     EXPECT_TRUE(allocationIsInternal(v));
@@ -78,9 +99,9 @@
         FatVector<TestUtils::SignalingDtor, 0> v;
         v.emplace_back(&count);
         EXPECT_FALSE(allocationIsInternal(v));
-        EXPECT_EQ(0, count);
+        EXPECT_EQ(0, count) << "Destruction shouldn't have happened yet";
     }
-    EXPECT_EQ(1, count);
+    EXPECT_EQ(1, count) << "Destruction should happen exactly once";
 }
 
 TEST(FatVector, destructorExternal) {
@@ -92,7 +113,7 @@
             v.emplace_back(&count);
             EXPECT_TRUE(allocationIsInternal(v));
         }
-        EXPECT_EQ(0, count);
+        EXPECT_EQ(0, count) << "Destruction shouldn't have happened yet";
     }
-    EXPECT_EQ(10, count);
+    EXPECT_EQ(10, count) << "Destruction should happen exactly once";
 }
diff --git a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
new file mode 100644
index 0000000..9d625bc
--- /dev/null
+++ b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
@@ -0,0 +1,82 @@
+/*
+ * 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 <LayerUpdateQueue.h>
+#include <RenderNode.h>
+
+#include <unit_tests/TestUtils.h>
+
+namespace android {
+namespace uirenderer {
+
+TEST(LayerUpdateQueue, construct) {
+    LayerUpdateQueue queue;
+    EXPECT_TRUE(queue.entries().empty());
+}
+
+// sync node properties, so properties() reflects correct width and height
+static sp<RenderNode> createSyncedNode(uint32_t width, uint32_t height) {
+    sp<RenderNode> node = TestUtils::createNode(0, 0, width, height);
+    TestUtils::syncNodePropertiesAndDisplayList(node);
+    return node;
+}
+
+TEST(LayerUpdateQueue, enqueueSimple) {
+    sp<RenderNode> a = createSyncedNode(100, 100);
+    sp<RenderNode> b = createSyncedNode(200, 200);
+
+    LayerUpdateQueue queue;
+    queue.enqueueLayerWithDamage(a.get(), Rect(25, 25, 75, 75));
+    queue.enqueueLayerWithDamage(b.get(), Rect(100, 100, 300, 300));
+
+    EXPECT_EQ(2u, queue.entries().size());
+
+    EXPECT_EQ(a.get(), queue.entries()[0].renderNode);
+    EXPECT_EQ(Rect(25, 25, 75, 75), queue.entries()[0].damage);
+    EXPECT_EQ(b.get(), queue.entries()[1].renderNode);
+    EXPECT_EQ(Rect(100, 100, 200, 200), queue.entries()[1].damage); // clipped to bounds
+}
+
+TEST(LayerUpdateQueue, enqueueUnion) {
+    sp<RenderNode> a = createSyncedNode(100, 100);
+
+    LayerUpdateQueue queue;
+    queue.enqueueLayerWithDamage(a.get(), Rect(10, 10, 20, 20));
+    queue.enqueueLayerWithDamage(a.get(), Rect(30, 30, 40, 40));
+
+    EXPECT_EQ(1u, queue.entries().size());
+
+    EXPECT_EQ(a.get(), queue.entries()[0].renderNode);
+    EXPECT_EQ(Rect(10, 10, 40, 40), queue.entries()[0].damage);
+}
+
+TEST(LayerUpdateQueue, clear) {
+    sp<RenderNode> a = createSyncedNode(100, 100);
+
+    LayerUpdateQueue queue;
+    queue.enqueueLayerWithDamage(a.get(), Rect(100, 100));
+
+    EXPECT_FALSE(queue.entries().empty());
+
+    queue.clear();
+
+    EXPECT_TRUE(queue.entries().empty());
+}
+
+};
+};
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index ffb575f..09b10c3 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -20,6 +20,7 @@
 #include <OpReorderer.h>
 #include <RecordedOp.h>
 #include <RecordingCanvas.h>
+#include <renderthread/CanvasContext.h> // todo: remove
 #include <unit_tests/TestUtils.h>
 
 #include <unordered_map>
@@ -27,6 +28,7 @@
 namespace android {
 namespace uirenderer {
 
+LayerUpdateQueue sEmptyLayerUpdateQueue;
 
 /**
  * Virtual class implemented by each test to redirect static operation / state transitions to
@@ -42,14 +44,24 @@
 class TestRendererBase {
 public:
     virtual ~TestRendererBase() {}
-    virtual OffscreenBuffer* startLayer(uint32_t width, uint32_t height) { ADD_FAILURE(); return nullptr; }
-    virtual void endLayer() { ADD_FAILURE(); }
+    virtual OffscreenBuffer* createLayer(uint32_t, uint32_t) {
+        ADD_FAILURE() << "Layer creation not expected in this test";
+        return nullptr;
+    }
+    virtual void startLayer(OffscreenBuffer*) {
+        ADD_FAILURE() << "Layer repaint not expected in this test";
+    }
+    virtual void endLayer() {
+        ADD_FAILURE() << "Layer updates not expected in this test";
+    }
     virtual void startFrame(uint32_t width, uint32_t height) {}
     virtual void endFrame() {}
 
     // define virtual defaults for direct
 #define BASE_OP_METHOD(Type) \
-    virtual void on##Type(const Type&, const BakedOpState&) { ADD_FAILURE(); }
+    virtual void on##Type(const Type&, const BakedOpState&) { \
+        ADD_FAILURE() << #Type " not expected in this test"; \
+    }
     MAP_OPS(BASE_OP_METHOD)
     int getIndex() { return mIndex; }
 
@@ -192,7 +204,8 @@
     std::vector< sp<RenderNode> > nodes;
     nodes.push_back(parent.get());
 
-    OpReorderer reorderer(SkRect::MakeWH(200, 200), 200, 200, nodes);
+    OpReorderer reorderer(sEmptyLayerUpdateQueue,
+            SkRect::MakeWH(200, 200), 200, 200, nodes);
 
     RenderNodeTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -216,7 +229,8 @@
     std::vector< sp<RenderNode> > nodes;
     nodes.push_back(node.get());
 
-    OpReorderer reorderer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
+    OpReorderer reorderer(sEmptyLayerUpdateQueue,
+            SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
             200, 200, nodes);
 
     ClippedTestRenderer renderer;
@@ -226,7 +240,7 @@
 
 class SaveLayerSimpleTestRenderer : public TestRendererBase {
 public:
-    OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override {
+    OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override {
         EXPECT_EQ(0, mIndex++);
         EXPECT_EQ(180u, width);
         EXPECT_EQ(180u, height);
@@ -268,13 +282,13 @@
 
 
 /* saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as:
- * - startLayer2, rect2 endLayer2
- * - startLayer1, rect1, drawLayer2, endLayer1
+ * - createLayer2, rect2 endLayer2
+ * - createLayer1, rect1, drawLayer2, endLayer1
  * - startFrame, layerOp1, endFrame
  */
 class SaveLayerNestedTestRenderer : public TestRendererBase {
 public:
-    OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override {
+    OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override {
         const int index = mIndex++;
         if (index == 0) {
             EXPECT_EQ(400u, width);
@@ -356,5 +370,162 @@
     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) {
+    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);
+    OffscreenBuffer** bufferHandle = node->getLayerHandle();
+    *bufferHandle = (OffscreenBuffer*) 0x0124;
+
+    TestUtils::syncNodePropertiesAndDisplayList(node);
+
+    std::vector< sp<RenderNode> > nodes;
+    nodes.push_back(node.get());
+
+    // only enqueue partial damage
+    LayerUpdateQueue layerUpdateQueue;
+    layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
+
+    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+
+    HwLayerSimpleTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(6, renderer.getIndex());
+
+    // clean up layer pointer, so we can safely destruct RenderNode
+    *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) {
+    sp<RenderNode> 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);
+    *(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567;
+
+    RenderNode* childPtr = child.get();
+    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setColor(SK_ColorDKGRAY);
+        canvas.drawRect(0, 0, 200, 200, paint);
+
+        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);
+    *(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123;
+
+    TestUtils::syncNodePropertiesAndDisplayList(child);
+    TestUtils::syncNodePropertiesAndDisplayList(parent);
+
+    std::vector< sp<RenderNode> > nodes;
+    nodes.push_back(parent.get());
+
+    LayerUpdateQueue layerUpdateQueue;
+    layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
+    layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200));
+
+    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+
+    HwLayerComplexTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(13, renderer.getIndex());
+
+    // clean up layer pointers, so we can safely destruct RenderNodes
+    *(child->getLayerHandle()) = nullptr;
+    *(parent->getLayerHandle()) = nullptr;
+}
+
 } // namespace uirenderer
 } // namespace android
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index e8cdf46..dcf1f64 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -53,7 +53,7 @@
         ASSERT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
         ASSERT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
     });
-    ASSERT_EQ(1, count); // only one observed
+    ASSERT_EQ(1, count);
 }
 
 TEST(RecordingCanvas, backgroundAndImage) {
@@ -106,7 +106,7 @@
         }
         count++;
     });
-    ASSERT_EQ(2, count); // two draws observed
+    ASSERT_EQ(2, count);
 }
 
 TEST(RecordingCanvas, saveLayerSimple) {
@@ -121,7 +121,9 @@
         switch(count++) {
         case 0:
             EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
-            // TODO: add asserts
+            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
+            EXPECT_EQ(Rect(0, 0, 200, 200), op.localClipRect);
+            EXPECT_TRUE(op.localMatrix.isIdentity());
             break;
         case 1:
             EXPECT_EQ(RecordedOpId::RectOp, op.opId);
@@ -132,7 +134,7 @@
             break;
         case 2:
             EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
-            // TODO: add asserts
+            // Don't bother asserting recording state data - it's not used
             break;
         default:
             ADD_FAILURE();
@@ -155,10 +157,8 @@
         if (count++ == 1) {
             Matrix4 expectedMatrix;
             EXPECT_EQ(RecordedOpId::RectOp, op.opId);
-
-            // recorded clip rect should be intersection of
-            // viewport and saveLayer bounds, in layer space
-            EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect);
+            EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect) << "Recorded clip rect should be"
+                    " intersection of viewport and saveLayer bounds, in layer space";
             EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds);
             expectedMatrix.loadTranslate(-100, -100, 0);
             EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
@@ -183,14 +183,11 @@
     int count = 0;
     playbackOps(*dl, [&count](const RecordedOp& op) {
         if (count++ == 1) {
-            Matrix4 expectedMatrix;
             EXPECT_EQ(RecordedOpId::RectOp, op.opId);
-
-            // recorded rect doesn't see rotate, since recorded relative to saveLayer bounds
             EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect);
             EXPECT_EQ(Rect(0, 0, 100, 100), op.unmappedBounds);
-            expectedMatrix.loadIdentity();
-            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+            EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix)
+                    << "Recorded op shouldn't see any canvas transform before the saveLayer";
         }
     });
     EXPECT_EQ(3, count);
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index 5b09fda..770f413 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -89,15 +89,24 @@
         return std::unique_ptr<DisplayList>(canvas.finishRecording());
     }
 
-    template<class CanvasType>
-    static sp<RenderNode> createNode(int left, int top, int right, int bottom,
-            std::function<void(CanvasType& canvas)> canvasCallback) {
+    static sp<RenderNode> createNode(int left, int top, int right, int bottom, bool onLayer = false) {
         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);
+        }
+        return node;
+    }
 
-        CanvasType canvas(
-                node->stagingProperties().getWidth(), node->stagingProperties().getHeight());
+    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);
+
+        auto&& props = node->stagingProperties(); // staging, since not sync'd yet
+        CanvasType canvas(props.getWidth(), props.getHeight());
         canvasCallback(canvas);
         node->setStagingDisplayList(canvas.finishRecording());
         return node;
@@ -108,7 +117,7 @@
         node->syncDisplayList();
     }
 
-    typedef std::function<void(RenderState& state, Caches& caches)> RtCallback;
+    typedef std::function<void(renderthread::RenderThread& thread)> RtCallback;
 
     class TestTask : public renderthread::RenderTask {
     public:
@@ -120,7 +129,7 @@
             RenderState& renderState = renderthread::RenderThread::getInstance().renderState();
 
             renderState.onGLContextCreated();
-            rtCallback(renderState, Caches::getInstance());
+            rtCallback(renderthread::RenderThread::getInstance());
             renderState.onGLContextDestroyed();
         };
         RtCallback rtCallback;
diff --git a/libs/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h
index c3c16c5a..315c249 100644
--- a/libs/hwui/utils/FatVector.h
+++ b/libs/hwui/utils/FatVector.h
@@ -91,6 +91,10 @@
             InlineStdAllocator<T, SIZE>(mAllocation)) {
         this->reserve(SIZE);
     }
+
+    FatVector(size_t capacity) : FatVector() {
+        this->resize(capacity);
+    }
 private:
     typename InlineStdAllocator<T, SIZE>::Allocation mAllocation;
 };
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 7f22b8a..45529ef 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -221,8 +221,9 @@
      * @return An array of audio encodings (e.g. {@link AudioFormat#ENCODING_PCM_16BIT},
      * {@link AudioFormat#ENCODING_PCM_FLOAT}) supported by the audio device.
      * <code>ENCODING_PCM_FLOAT</code> indicates the device supports more
-     * than 16 bits of integer precision.  Specifying <code>ENCODING_PCM_FLOAT</code>
-     * with {@link AudioTrack} or {@link AudioRecord} can preserve at least 24 bits of
+     * than 16 bits of integer precision.  As there is no AudioFormat constant
+     * specifically defined for 24-bit PCM, the value <code>ENCODING_PCM_FLOAT</code>
+     * indicates that {@link AudioTrack} or {@link AudioRecord} can preserve at least 24 bits of
      * integer precision to that device.
      *
      * @see AudioFormat
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 875e716..50df556 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3199,8 +3199,10 @@
     /**
      * Returns the value of the property with the specified key.
      * @param key One of the strings corresponding to a property key: either
-     *            {@link #PROPERTY_OUTPUT_SAMPLE_RATE} or
-     *            {@link #PROPERTY_OUTPUT_FRAMES_PER_BUFFER}
+     *            {@link #PROPERTY_OUTPUT_SAMPLE_RATE},
+     *            {@link #PROPERTY_OUTPUT_FRAMES_PER_BUFFER},
+     *            {@link #PROPERTY_SUPPORT_MIC_NEAR_ULTRASOUND}, or
+     *            {@link #PROPERTY_SUPPORT_SPEAKER_NEAR_ULTRASOUND}.
      * @return A string representing the associated value for that property key,
      *         or null if there is no value for that key.
      */
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index 4019d02..ada7f49 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -72,10 +72,10 @@
             android:scrollbars="vertical"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:paddingStart="@dimen/grid_padding_horiz"
-            android:paddingEnd="@dimen/grid_padding_horiz"
-            android:paddingTop="@dimen/grid_padding_vert"
-            android:paddingBottom="@dimen/grid_padding_vert"
+            android:paddingStart="0dp"
+            android:paddingEnd="0dp"
+            android:paddingTop="0dp"
+            android:paddingBottom="0dp"
             android:clipToPadding="false"
             android:scrollbarStyle="outsideOverlay"
             android:drawSelectorOnTop="true"
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index a09a22a..3b7da78 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -66,7 +66,6 @@
 import android.support.v7.widget.RecyclerView.OnItemTouchListener;
 import android.support.v7.widget.RecyclerView.RecyclerListener;
 import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.support.v7.widget.SimpleItemAnimator;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.text.format.Formatter;
@@ -162,6 +161,9 @@
     private MessageBar mMessageBar;
     private View mProgressBar;
 
+    private int mSelectedItemColor;
+    private int mDefaultItemColor;
+
     public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
         show(fm, TYPE_NORMAL, root, doc, null, anim);
     }
@@ -254,8 +256,7 @@
                     }
                 });
 
-        // TODO: Restore transition animations.  See b/24802917.
-        ((SimpleItemAnimator) mRecView.getItemAnimator()).setSupportsChangeAnimations(false);
+        mRecView.setItemAnimator(new DirectoryItemAnimator(getActivity()));
 
         // TODO: Add a divider between views (which might use RecyclerView.ItemDecoration).
         if (DEBUG_ENABLE_DND) {
@@ -293,6 +294,13 @@
         mAdapter = new DocumentsAdapter(context);
         mRecView.setAdapter(mAdapter);
 
+        mDefaultItemColor = context.getResources().getColor(android.R.color.transparent);
+        // Get the accent color.
+        TypedValue selColor = new TypedValue();
+        context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true);
+        // Set the opacity to 10%.
+        mSelectedItemColor = (selColor.data & 0x00ffffff) | 0x16000000;
+
         GestureDetector.SimpleOnGestureListener listener =
                 new GestureDetector.SimpleOnGestureListener() {
                     @Override
@@ -669,7 +677,7 @@
             getActivity().getWindow().setStatusBarColor(color.data);
 
             if (mActionMode != null) {
-                mActionMode.setTitle(TextUtils.formatSelectedCount(mSelected.size()));
+                mActionMode.setTitle(String.valueOf(mSelected.size()));
             }
         }
 
@@ -897,24 +905,26 @@
     // Provide a reference to the views for each data item
     // Complex data items may need more than one view per item, and
     // you provide access to all the views for a data item in a view holder
-    private static final class DocumentHolder
+    private final class DocumentHolder
             extends RecyclerView.ViewHolder
             implements View.OnKeyListener
     {
-        // each data item is just a string in this case
-        public View view;
         public String docId;  // The stable document id.
         private ClickListener mClickListener;
         private View.OnKeyListener mKeyListener;
 
         public DocumentHolder(View view) {
             super(view);
-            this.view = view;
             // Setting this using android:focusable in the item layouts doesn't work for list items.
             // So we set it here.  Note that touch mode focus is a separate issue - see
             // View.setFocusableInTouchMode and View.isInTouchMode for more info.
-            this.view.setFocusable(true);
-            this.view.setOnKeyListener(this);
+            view.setFocusable(true);
+            view.setOnKeyListener(this);
+        }
+
+        public void setSelected(boolean selected) {
+            itemView.setActivated(selected);
+            itemView.setBackgroundColor(selected ? mSelectedItemColor : mDefaultItemColor);
         }
 
         @Override
@@ -943,10 +953,10 @@
             checkState(mKeyListener == null);
             mKeyListener = listener;
         }
+    }
 
-        interface ClickListener {
-            public void onClick(DocumentHolder doc);
-        }
+    interface ClickListener {
+        public void onClick(DocumentHolder doc);
     }
 
     void showEmptyView() {
@@ -1005,6 +1015,24 @@
             return holder;
         }
 
+        /**
+         * Deal with selection changed events by using a custom ItemAnimator that just changes the
+         * background color.  This works around focus issues (otherwise items lose focus when their
+         * selection state changes) but also optimizes change animations for selection.
+         */
+        @Override
+        public void onBindViewHolder(DocumentHolder holder, int position, List<Object> payload) {
+            final View itemView = holder.itemView;
+
+            if (payload.contains(MultiSelectManager.SELECTION_CHANGED_MARKER)) {
+                final boolean selected = isSelected(position);
+                itemView.setActivated(selected);
+                return;
+            } else {
+                onBindViewHolder(holder, position);
+            }
+        }
+
         @Override
         public void onBindViewHolder(DocumentHolder holder, int position) {
 
@@ -1030,8 +1058,9 @@
             final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
 
             holder.docId = docId;
-            final View itemView = holder.view;
-            itemView.setActivated(isSelected(position));
+            final View itemView = holder.itemView;
+
+            holder.setSelected(isSelected(position));
 
             final View line1 = itemView.findViewById(R.id.line1);
             final View line2 = itemView.findViewById(R.id.line2);
@@ -1959,7 +1988,7 @@
         }
     }
 
-    private class ItemClickListener implements DocumentHolder.ClickListener {
+    private class ItemClickListener implements ClickListener {
         @Override
         public void onClick(DocumentHolder doc) {
             final int position = doc.getAdapterPosition();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java
new file mode 100644
index 0000000..0eb1ea5
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import android.animation.Animator;
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.support.v4.util.ArrayMap;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.RecyclerView;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Performs change animations on Items in DirectoryFragment's RecyclerView.  This class overrides
+ * the way selection animations are normally performed - instead of cross fading the old Item with a
+ * new Item, this class manually animates a background color change.  This enables selected Items to
+ * correctly maintain focus.
+ */
+class DirectoryItemAnimator extends DefaultItemAnimator {
+    private final List<ColorAnimation> mPendingAnimations = new ArrayList<>();
+    private final Map<RecyclerView.ViewHolder, ColorAnimation> mRunningAnimations =
+            new ArrayMap<>();
+    private final Integer mDefaultColor;
+    private final Integer mSelectedColor;
+
+    public DirectoryItemAnimator(Context context) {
+        mDefaultColor = context.getResources().getColor(android.R.color.transparent);
+        // Get the accent color.
+        TypedValue selColor = new TypedValue();
+        context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true);
+        // Set the opacity to 10%.
+        mSelectedColor = (selColor.data & 0x00ffffff) | 0x16000000;
+    }
+
+    @Override
+    public void runPendingAnimations() {
+        super.runPendingAnimations();
+        for (ColorAnimation anim: mPendingAnimations) {
+            anim.start();
+            mRunningAnimations.put(anim.viewHolder, anim);
+        }
+        mPendingAnimations.clear();
+    }
+
+    @Override
+    public void endAnimation(RecyclerView.ViewHolder vh) {
+        super.endAnimation(vh);
+
+        for (int i = mPendingAnimations.size() - 1; i >= 0; --i) {
+            ColorAnimation anim = mPendingAnimations.get(i);
+            if (anim.viewHolder == vh) {
+                mPendingAnimations.remove(i);
+                anim.end();
+            }
+        }
+
+        ColorAnimation anim = mRunningAnimations.get(vh);
+        if (anim != null) {
+            anim.cancel();
+        }
+    }
+
+    @Override
+    public ItemHolderInfo recordPreLayoutInformation(
+        RecyclerView.State state,
+        RecyclerView.ViewHolder viewHolder,
+        @AdapterChanges int changeFlags,
+        List<Object> payloads) {
+        ItemInfo info = (ItemInfo) super.recordPreLayoutInformation(state,
+                viewHolder, changeFlags, payloads);
+        info.isActivated = viewHolder.itemView.isActivated();
+        return info;
+    }
+
+
+    @Override
+    public ItemHolderInfo recordPostLayoutInformation(
+        RecyclerView.State state, RecyclerView.ViewHolder viewHolder) {
+        ItemInfo info = (ItemInfo) super.recordPostLayoutInformation(state,
+                viewHolder);
+        info.isActivated = viewHolder.itemView.isActivated();
+        return info;
+    }
+
+    @Override
+    public boolean animateChange(final RecyclerView.ViewHolder oldHolder,
+            RecyclerView.ViewHolder newHolder, ItemHolderInfo preInfo,
+            ItemHolderInfo postInfo) {
+        if (oldHolder != newHolder) {
+            return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
+        }
+
+        ItemInfo pre = (ItemInfo)preInfo;
+        ItemInfo post = (ItemInfo)postInfo;
+
+        if (pre.isActivated == post.isActivated) {
+            dispatchAnimationFinished(oldHolder);
+            return false;
+        } else {
+            Integer startColor = pre.isActivated ? mSelectedColor : mDefaultColor;
+            Integer endColor = post.isActivated ? mSelectedColor : mDefaultColor;
+            oldHolder.itemView.setBackgroundColor(startColor);
+            mPendingAnimations.add(new ColorAnimation(oldHolder, startColor, endColor));
+        }
+        return true;
+    }
+
+    @Override
+    public ItemHolderInfo obtainHolderInfo() {
+        return new ItemInfo();
+    }
+
+    @Override
+    public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder vh) {
+        return true;
+    }
+
+    class ItemInfo extends DefaultItemAnimator.ItemHolderInfo {
+        boolean isActivated;
+    };
+
+    /**
+     * Animates changes in background color.
+     */
+    class ColorAnimation
+            implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
+        ValueAnimator mValueAnimator;
+        final RecyclerView.ViewHolder viewHolder;
+        int mEndColor;
+
+        public ColorAnimation(RecyclerView.ViewHolder vh, int startColor, int endColor)
+        {
+            viewHolder = vh;
+            mValueAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), startColor, endColor);
+            mValueAnimator.addUpdateListener(this);
+            mValueAnimator.addListener(this);
+
+            mEndColor = endColor;
+        }
+
+        public void start() {
+            mValueAnimator.start();
+        }
+
+        public void cancel() {
+            mValueAnimator.cancel();
+        }
+
+        public void end() {
+            mValueAnimator.end();
+        }
+
+        @Override
+        public void onAnimationUpdate(ValueAnimator animator) {
+            viewHolder.itemView.setBackgroundColor((Integer)animator.getAnimatedValue());
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animator) {
+            viewHolder.itemView.setBackgroundColor(mEndColor);
+            mRunningAnimations.remove(viewHolder);
+            dispatchAnimationFinished(viewHolder);
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            dispatchAnimationStarted(viewHolder);
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {}
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {}
+    };
+};
diff --git a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java b/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
index 14a33f9..26a3734 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
@@ -34,6 +34,7 @@
 import android.support.design.widget.Snackbar;
 import android.util.Log;
 import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
 import android.widget.BaseAdapter;
 import android.widget.Spinner;
@@ -49,7 +50,6 @@
 import java.util.List;
 
 public class ManageRootActivity extends BaseActivity {
-    private static final int CODE_FORWARD = 42;
     private static final String TAG = "ManageRootsActivity";
 
     private Toolbar mToolbar;
@@ -140,6 +140,21 @@
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
         super.onPrepareOptionsMenu(menu);
+
+        final MenuItem advanced = menu.findItem(R.id.menu_advanced);
+        final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
+        final MenuItem newWindow = menu.findItem(R.id.menu_new_window);
+        final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard);
+        final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
+        final MenuItem search = menu.findItem(R.id.menu_search);
+
+        advanced.setVisible(false);
+        createDir.setVisible(false);
+        pasteFromCb.setEnabled(false);
+        newWindow.setEnabled(false);
+        fileSize.setVisible(false);
+        search.setVisible(false);
+
         Menus.disableHiddenItems(menu);
         return true;
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
index ef53d53..4fde6ff 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
@@ -76,6 +76,9 @@
     private Adapter<?> mAdapter;
     private boolean mSingleSelect;
 
+    // Payloads for notifyItemChange to distinguish between selection and other events.
+    public static final String SELECTION_CHANGED_MARKER = "Selection-Changed";
+
     @Nullable private BandController mBandManager;
 
     /**
@@ -460,7 +463,7 @@
         for (int i = lastListener; i > -1; i--) {
             mCallbacks.get(i).onItemStateChanged(position, selected);
         }
-        mAdapter.notifyItemChanged(position);
+        mAdapter.notifyItemChanged(position, SELECTION_CHANGED_MARKER);
     }
 
     /**
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index d75b6fd..beff196 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -16,6 +16,8 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.Shared.DEBUG;
+
 import android.app.Fragment;
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
@@ -30,6 +32,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.format.Formatter;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -56,12 +59,13 @@
  */
 public class RootsFragment extends Fragment {
 
+    private static final String TAG = "RootsFragment";
+    private static final String EXTRA_INCLUDE_APPS = "includeApps";
+
     private ListView mList;
     private RootsAdapter mAdapter;
-
     private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
 
-    private static final String EXTRA_INCLUDE_APPS = "includeApps";
 
     public static void show(FragmentManager fm, Intent includeApps) {
         final Bundle args = new Bundle();
@@ -180,6 +184,8 @@
             } else if (item instanceof AppItem) {
                 DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
                 activity.onAppPicked(((AppItem) item).info);
+            } else if (item instanceof SpacerItem) {
+                if (DEBUG) Log.d(TAG, "Ignoring click on spacer item.");
             } else {
                 throw new IllegalStateException("Unknown root: " + item);
             }
diff --git a/packages/DocumentsUI/tests/Android.mk b/packages/DocumentsUI/tests/Android.mk
index 2a540d4..b65ac98 100644
--- a/packages/DocumentsUI/tests/Android.mk
+++ b/packages/DocumentsUI/tests/Android.mk
@@ -8,7 +8,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target guava ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target ub-uiautomator
 
 LOCAL_PACKAGE_NAME := DocumentsUITests
 LOCAL_INSTRUMENTATION_FOR := DocumentsUI
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
index fc42c3b..6f1a89b 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
@@ -28,6 +28,7 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
 import android.test.MoreAsserts;
 import android.test.ServiceTestCase;
 import android.test.mock.MockContentResolver;
@@ -36,6 +37,7 @@
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.RootInfo;
+
 import com.google.common.collect.Lists;
 
 import libcore.io.IoUtils;
@@ -52,6 +54,404 @@
 
 public class CopyTest extends ServiceTestCase<CopyService> {
 
+    public CopyTest() {
+        super(CopyService.class);
+    }
+
+    private static String AUTHORITY = "com.android.documentsui.stubprovider";
+    private static String SRC_ROOT = StubProvider.ROOT_0_ID;
+    private static String DST_ROOT = StubProvider.ROOT_1_ID;
+    private static String TAG = "CopyTest";
+
+    private Context mContext;
+    private TestContentResolver mResolver;
+    private ContentProviderClient mClient;
+    private DocumentsProviderHelper mDocHelper;
+    private StubProvider mStorage;
+    private Context mSystemContext;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        setupTestContext();
+        mClient = mResolver.acquireContentProviderClient(AUTHORITY);
+
+        // Reset the stub provider's storage.
+        mStorage.clearCacheAndBuildRoots();
+
+        mDocHelper = new DocumentsProviderHelper(AUTHORITY, mClient);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mClient.release();
+        super.tearDown();
+    }
+
+    /**
+     * Test copying a single file.
+     */
+    public void testCopyFile() throws Exception {
+        String srcPath = "/test0.txt";
+        Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
+                "The five boxing wizards jump quickly".getBytes());
+
+        assertDstFileCountEquals(0);
+
+        startService(createCopyIntent(Lists.newArrayList(testFile)));
+
+        // 2 operations: file creation, then writing data.
+        mResolver.waitForChanges(2);
+
+        // Verify that one file was copied; check file contents.
+        assertDstFileCountEquals(1);
+        assertCopied(srcPath);
+    }
+
+    public void testMoveFile() throws Exception {
+        String srcPath = "/test0.txt";
+        String testContent = "The five boxing wizards jump quickly";
+        Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", testContent.getBytes());
+
+        assertDstFileCountEquals(0);
+
+        Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
+        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+        startService(moveIntent);
+
+        // 3 operations: file creation, writing data, deleting original.
+        mResolver.waitForChanges(3);
+
+        // Verify that one file was moved; check file contents.
+        assertDstFileCountEquals(1);
+        assertDoesNotExist(SRC_ROOT, srcPath);
+
+        byte[] dstContent = readFile(DST_ROOT, srcPath);
+        MoreAsserts.assertEquals("Moved file contents differ", testContent.getBytes(), dstContent);
+    }
+
+    /**
+     * Test copying multiple files.
+     */
+    public void testCopyMultipleFiles() throws Exception {
+        String testContent[] = {
+                "The five boxing wizards jump quickly",
+                "The quick brown fox jumps over the lazy dog",
+                "Jackdaws love my big sphinx of quartz"
+        };
+        String srcPaths[] = {
+                "/test0.txt",
+                "/test1.txt",
+                "/test2.txt"
+        };
+        List<Uri> testFiles = Lists.newArrayList(
+                mStorage.createFile(SRC_ROOT, srcPaths[0], "text/plain", testContent[0].getBytes()),
+                mStorage.createFile(SRC_ROOT, srcPaths[1], "text/plain", testContent[1].getBytes()),
+                mStorage.createFile(SRC_ROOT, srcPaths[2], "text/plain", testContent[2].getBytes()));
+
+        assertDstFileCountEquals(0);
+
+        // Copy all the test files.
+        startService(createCopyIntent(testFiles));
+
+        // 3 file creations, 3 file writes.
+        mResolver.waitForChanges(6);
+
+        assertDstFileCountEquals(3);
+        for (String path : srcPaths) {
+            assertCopied(path);
+        }
+    }
+
+    public void testCopyEmptyDir() throws Exception {
+        String srcPath = "/emptyDir";
+        Uri testDir = mStorage.createFile(SRC_ROOT, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
+                null);
+
+        assertDstFileCountEquals(0);
+
+        startService(createCopyIntent(Lists.newArrayList(testDir)));
+
+        // Just 1 operation: Directory creation.
+        mResolver.waitForChanges(1);
+
+        assertDstFileCountEquals(1);
+
+        // Verify that the dst exists and is a directory.
+        File dst = mStorage.getFile(DST_ROOT, srcPath);
+        assertTrue(dst.isDirectory());
+    }
+
+    public void testMoveEmptyDir() throws Exception {
+        String srcPath = "/emptyDir";
+        Uri testDir = mStorage.createFile(SRC_ROOT, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
+                null);
+
+        assertDstFileCountEquals(0);
+
+        Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
+        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+        startService(moveIntent);
+
+        // 2 operations: Directory creation, and removal of the original.
+        mResolver.waitForChanges(2);
+
+        assertDstFileCountEquals(1);
+
+        // Verify that the dst exists and is a directory.
+        File dst = mStorage.getFile(DST_ROOT, srcPath);
+        assertTrue(dst.isDirectory());
+
+        // Verify that the src was cleaned up.
+        assertDoesNotExist(SRC_ROOT, srcPath);
+    }
+
+    public void testMovePopulatedDir() throws Exception {
+        String testContent[] = {
+                "The five boxing wizards jump quickly",
+                "The quick brown fox jumps over the lazy dog",
+                "Jackdaws love my big sphinx of quartz"
+        };
+        String srcDir = "/testdir";
+        String srcFiles[] = {
+                srcDir + "/test0.txt",
+                srcDir + "/test1.txt",
+                srcDir + "/test2.txt"
+        };
+        // Create test dir; put some files in it.
+        Uri testDir = mStorage.createFile(SRC_ROOT, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
+                null);
+        mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
+        mStorage.createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
+        mStorage.createFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes());
+
+        Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
+        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+        startService(moveIntent);
+
+        // dir creation, then creation and writing of 3 files, then removal of src dir and 3 src
+        // files.
+        mResolver.waitForChanges(11);
+
+        // Check the content of the moved files.
+        File dst = mStorage.getFile(DST_ROOT, srcDir);
+        assertTrue(dst.isDirectory());
+        for (int i = 0; i < testContent.length; ++i) {
+            byte[] dstContent = readFile(DST_ROOT, srcFiles[i]);
+            MoreAsserts.assertEquals("Copied file contents differ", testContent[i].getBytes(),
+                    dstContent);
+        }
+
+        // Check that the src files were removed.
+        assertDoesNotExist(SRC_ROOT, srcDir);
+        for (String srcFile : srcFiles) {
+            assertDoesNotExist(SRC_ROOT, srcFile);
+        }
+    }
+
+    public void testCopyFileWithReadErrors() throws Exception {
+        String srcPath = "/test0.txt";
+        Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
+                "The five boxing wizards jump quickly".getBytes());
+
+        assertDstFileCountEquals(0);
+
+        mStorage.simulateReadErrorsForFile(testFile);
+
+        startService(createCopyIntent(Lists.newArrayList(testFile)));
+
+        // 3 operations: file creation, writing, then deletion (due to failed copy).
+        mResolver.waitForChanges(3);
+
+        // Verify that the failed copy was cleaned up.
+        assertDstFileCountEquals(0);
+    }
+
+    public void testMoveFileWithReadErrors() throws Exception {
+        String srcPath = "/test0.txt";
+        Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
+                "The five boxing wizards jump quickly".getBytes());
+
+        assertDstFileCountEquals(0);
+
+        mStorage.simulateReadErrorsForFile(testFile);
+
+        Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
+        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+        startService(moveIntent);
+
+        try {
+            // There should be 3 operations: file creation, writing, then deletion (due to failed
+            // copy). Wait for 4, in case the CopyService also attempts to do extra stuff (like
+            // delete the src file). This should time out.
+            mResolver.waitForChanges(4);
+        } catch (TimeoutException e) {
+            // Success path
+            return;
+        } finally {
+            // Verify that the failed copy was cleaned up, and the src file wasn't removed.
+            assertDstFileCountEquals(0);
+            assertExists(SRC_ROOT, srcPath);
+        }
+        // The asserts above didn't fail, but the CopyService did something unexpected.
+        fail("Extra file operations were detected");
+    }
+
+    public void testMoveDirectoryWithReadErrors() throws Exception {
+        String testContent[] = {
+                "The five boxing wizards jump quickly",
+                "The quick brown fox jumps over the lazy dog",
+                "Jackdaws love my big sphinx of quartz"
+        };
+        String srcDir = "/testdir";
+        String srcFiles[] = {
+                srcDir + "/test0.txt",
+                srcDir + "/test1.txt",
+                srcDir + "/test2.txt"
+        };
+        // Create test dir; put some files in it.
+        Uri testDir = mStorage.createFile(SRC_ROOT, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
+                null);
+        mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
+        Uri errFile = mStorage
+                .createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
+        mStorage.createFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes());
+
+        mStorage.simulateReadErrorsForFile(errFile);
+
+        Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
+        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+        startService(moveIntent);
+
+        // - dst dir creation,
+        // - creation and writing of 2 files, removal of 2 src files
+        // - creation and writing of 1 file, then removal of that file (due to error)
+        mResolver.waitForChanges(10);
+
+        // Check that both the src and dst dirs exist. The src dir shouldn't have been removed,
+        // because it should contain the one errFile.
+        assertTrue(mStorage.getFile(SRC_ROOT, srcDir).isDirectory());
+        assertTrue(mStorage.getFile(DST_ROOT, srcDir).isDirectory());
+
+        // Check the content of the moved files.
+        MoreAsserts.assertEquals("Copied file contents differ", testContent[0].getBytes(),
+                readFile(DST_ROOT, srcFiles[0]));
+        MoreAsserts.assertEquals("Copied file contents differ", testContent[2].getBytes(),
+                readFile(DST_ROOT, srcFiles[2]));
+
+        // Check that the src files were removed.
+        assertDoesNotExist(SRC_ROOT, srcFiles[0]);
+        assertDoesNotExist(SRC_ROOT, srcFiles[2]);
+
+        // Check that the error file was not copied over.
+        assertDoesNotExist(DST_ROOT, srcFiles[1]);
+        assertExists(SRC_ROOT, srcFiles[1]);
+    }
+
+    /**
+     * Copies the given files to a pre-determined destination.
+     *
+     * @throws FileNotFoundException
+     */
+    private Intent createCopyIntent(List<Uri> srcs) throws Exception {
+        final ArrayList<DocumentInfo> srcDocs = Lists.newArrayList();
+        for (Uri src : srcs) {
+            srcDocs.add(DocumentInfo.fromUri(mResolver, src));
+        }
+
+        RootInfo root = mDocHelper.getRoot(DST_ROOT);
+        final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, root.documentId);
+        DocumentStack stack = new DocumentStack();
+        stack.push(DocumentInfo.fromUri(mResolver, dst));
+        final Intent copyIntent = new Intent(mContext, CopyService.class);
+        copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST, srcDocs);
+        copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+
+        // startService(copyIntent);
+        return copyIntent;
+    }
+
+    /**
+     * Returns a count of the files in the given directory.
+     */
+    private void assertDstFileCountEquals(int expected) throws RemoteException {
+        RootInfo dest = mDocHelper.getRoot(DST_ROOT);
+        final Uri queryUri = DocumentsContract.buildChildDocumentsUri(AUTHORITY,
+                dest.documentId);
+        Cursor c = null;
+        int count = 0;
+        try {
+            c = mClient.query(queryUri, null, null, null, null);
+            count = c.getCount();
+        } finally {
+            IoUtils.closeQuietly(c);
+        }
+        assertEquals("Incorrect file count after copy", expected, count);
+    }
+
+    private void assertExists(String rootId, String path) throws Exception {
+        assertNotNull("An expected file was not found: " + path + " on root " + rootId,
+                mStorage.getFile(rootId, path));
+    }
+
+    private void assertDoesNotExist(String rootId, String path) throws Exception {
+        assertNull("Unexpected file found: " + path + " on root " + rootId,
+                mStorage.getFile(rootId, path));
+    }
+
+    private byte[] readFile(String rootId, String path) throws Exception {
+        File file = mStorage.getFile(rootId, path);
+        byte[] buf = null;
+        assertNotNull(file);
+
+        FileInputStream in = null;
+        try {
+            in = new FileInputStream(file);
+            buf = Streams.readFully(in);
+        } finally {
+            IoUtils.closeQuietly(in);
+        }
+        return buf;
+    }
+
+    private void assertCopied(String path) throws Exception {
+        MoreAsserts.assertEquals("Copied file contents differ", readFile(SRC_ROOT, path),
+                readFile(DST_ROOT, path));
+    }
+
+    /**
+     * Sets up a ContextWrapper that substitutes a stub NotificationManager. This allows the test to
+     * listen for notification events, to gauge copy progress.
+     *
+     * @throws FileNotFoundException
+     */
+    private void setupTestContext() throws FileNotFoundException {
+        mSystemContext = getSystemContext();
+
+        // Set up the context with the test content resolver.
+        mResolver = new TestContentResolver();
+        mContext = new ContextWrapper(mSystemContext) {
+            @Override
+            public ContentResolver getContentResolver() {
+                return mResolver;
+            }
+        };
+        setContext(mContext);
+
+        // Create a local stub provider and add it to the content resolver.
+        ProviderInfo info = new ProviderInfo();
+        info.authority = AUTHORITY;
+        info.exported = true;
+        info.grantUriPermissions = true;
+        info.readPermission = android.Manifest.permission.MANAGE_DOCUMENTS;
+        info.writePermission = android.Manifest.permission.MANAGE_DOCUMENTS;
+
+        mStorage = new StubProvider();
+        mStorage.attachInfo(mContext, info);
+        mResolver.addProvider(AUTHORITY, mStorage);
+    }
+
     /**
      * A test resolver that enables this test suite to listen for notifications that mark when copy
      * operations are done.
@@ -103,410 +503,4 @@
             }
         }
     };
-
-    public CopyTest() {
-        super(CopyService.class);
-    }
-
-    private static String AUTHORITY = "com.android.documentsui.stubprovider";
-    private static String DST = "sd1";
-    private static String SRC = "sd0";
-    private static String TAG = "CopyTest";
-    private List<RootInfo> mRoots;
-    private Context mContext;
-    private TestContentResolver mResolver;
-    private ContentProviderClient mClient;
-    private StubProvider mStorage;
-    private Context mSystemContext;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        setupTestContext();
-        mClient = mResolver.acquireContentProviderClient(AUTHORITY);
-
-        // Reset the stub provider's storage.
-        mStorage.clearCacheAndBuildRoots();
-
-        mRoots = Lists.newArrayList();
-        Uri queryUri = DocumentsContract.buildRootsUri(AUTHORITY);
-        Cursor cursor = null;
-        try {
-            cursor = mClient.query(queryUri, null, null, null, null);
-            while (cursor.moveToNext()) {
-                mRoots.add(RootInfo.fromRootsCursor(AUTHORITY, cursor));
-            }
-        } finally {
-            IoUtils.closeQuietly(cursor);
-        }
-
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mClient.release();
-        super.tearDown();
-    }
-
-    /**
-     * Test copying a single file.
-     */
-    public void testCopyFile() throws Exception {
-        String srcPath = "/test0.txt";
-        Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain",
-                "The five boxing wizards jump quickly".getBytes());
-
-        assertDstFileCountEquals(0);
-
-        startService(createCopyIntent(Lists.newArrayList(testFile)));
-
-        // 2 operations: file creation, then writing data.
-        mResolver.waitForChanges(2);
-
-        // Verify that one file was copied; check file contents.
-        assertDstFileCountEquals(1);
-        assertCopied(srcPath);
-    }
-
-    public void testMoveFile() throws Exception {
-        String srcPath = "/test0.txt";
-        String testContent = "The five boxing wizards jump quickly";
-        Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain", testContent.getBytes());
-
-        assertDstFileCountEquals(0);
-
-        Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
-        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
-        startService(moveIntent);
-
-        // 3 operations: file creation, writing data, deleting original.
-        mResolver.waitForChanges(3);
-
-        // Verify that one file was moved; check file contents.
-        assertDstFileCountEquals(1);
-        assertDoesNotExist(SRC, srcPath);
-
-        byte[] dstContent = readFile(DST, srcPath);
-        MoreAsserts.assertEquals("Moved file contents differ", testContent.getBytes(), dstContent);
-    }
-
-    /**
-     * Test copying multiple files.
-     */
-    public void testCopyMultipleFiles() throws Exception {
-        String testContent[] = {
-                "The five boxing wizards jump quickly",
-                "The quick brown fox jumps over the lazy dog",
-                "Jackdaws love my big sphinx of quartz"
-        };
-        String srcPaths[] = {
-                "/test0.txt",
-                "/test1.txt",
-                "/test2.txt"
-        };
-        List<Uri> testFiles = Lists.newArrayList(
-                mStorage.createFile(SRC, srcPaths[0], "text/plain", testContent[0].getBytes()),
-                mStorage.createFile(SRC, srcPaths[1], "text/plain", testContent[1].getBytes()),
-                mStorage.createFile(SRC, srcPaths[2], "text/plain", testContent[2].getBytes()));
-
-        assertDstFileCountEquals(0);
-
-        // Copy all the test files.
-        startService(createCopyIntent(testFiles));
-
-        // 3 file creations, 3 file writes.
-        mResolver.waitForChanges(6);
-
-        assertDstFileCountEquals(3);
-        for (String path : srcPaths) {
-            assertCopied(path);
-        }
-    }
-
-    public void testCopyEmptyDir() throws Exception {
-        String srcPath = "/emptyDir";
-        Uri testDir = mStorage.createFile(SRC, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
-                null);
-
-        assertDstFileCountEquals(0);
-
-        startService(createCopyIntent(Lists.newArrayList(testDir)));
-
-        // Just 1 operation: Directory creation.
-        mResolver.waitForChanges(1);
-
-        assertDstFileCountEquals(1);
-
-        // Verify that the dst exists and is a directory.
-        File dst = mStorage.getFile(DST, srcPath);
-        assertTrue(dst.isDirectory());
-    }
-
-    public void testMoveEmptyDir() throws Exception {
-        String srcPath = "/emptyDir";
-        Uri testDir = mStorage.createFile(SRC, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
-                null);
-
-        assertDstFileCountEquals(0);
-
-        Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
-        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
-        startService(moveIntent);
-
-        // 2 operations: Directory creation, and removal of the original.
-        mResolver.waitForChanges(2);
-
-        assertDstFileCountEquals(1);
-
-        // Verify that the dst exists and is a directory.
-        File dst = mStorage.getFile(DST, srcPath);
-        assertTrue(dst.isDirectory());
-
-        // Verify that the src was cleaned up.
-        assertDoesNotExist(SRC, srcPath);
-    }
-
-    public void testMovePopulatedDir() throws Exception {
-        String testContent[] = {
-                "The five boxing wizards jump quickly",
-                "The quick brown fox jumps over the lazy dog",
-                "Jackdaws love my big sphinx of quartz"
-        };
-        String srcDir = "/testdir";
-        String srcFiles[] = {
-                srcDir + "/test0.txt",
-                srcDir + "/test1.txt",
-                srcDir + "/test2.txt"
-        };
-        // Create test dir; put some files in it.
-        Uri testDir = mStorage.createFile(SRC, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
-                null);
-        mStorage.createFile(SRC, srcFiles[0], "text/plain", testContent[0].getBytes());
-        mStorage.createFile(SRC, srcFiles[1], "text/plain", testContent[1].getBytes());
-        mStorage.createFile(SRC, srcFiles[2], "text/plain", testContent[2].getBytes());
-
-        Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
-        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
-        startService(moveIntent);
-
-        // dir creation, then creation and writing of 3 files, then removal of src dir and 3 src
-        // files.
-        mResolver.waitForChanges(11);
-
-        // Check the content of the moved files.
-        File dst = mStorage.getFile(DST, srcDir);
-        assertTrue(dst.isDirectory());
-        for (int i = 0; i < testContent.length; ++i) {
-            byte[] dstContent = readFile(DST, srcFiles[i]);
-            MoreAsserts.assertEquals("Copied file contents differ", testContent[i].getBytes(),
-                    dstContent);
-        }
-
-        // Check that the src files were removed.
-        assertDoesNotExist(SRC, srcDir);
-        for (String srcFile : srcFiles) {
-            assertDoesNotExist(SRC, srcFile);
-        }
-    }
-
-    public void testCopyFileWithReadErrors() throws Exception {
-        String srcPath = "/test0.txt";
-        Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain",
-                "The five boxing wizards jump quickly".getBytes());
-
-        assertDstFileCountEquals(0);
-
-        mStorage.simulateReadErrorsForFile(testFile);
-
-        startService(createCopyIntent(Lists.newArrayList(testFile)));
-
-        // 3 operations: file creation, writing, then deletion (due to failed copy).
-        mResolver.waitForChanges(3);
-
-        // Verify that the failed copy was cleaned up.
-        assertDstFileCountEquals(0);
-    }
-
-    public void testMoveFileWithReadErrors() throws Exception {
-        String srcPath = "/test0.txt";
-        Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain",
-                "The five boxing wizards jump quickly".getBytes());
-
-        assertDstFileCountEquals(0);
-
-        mStorage.simulateReadErrorsForFile(testFile);
-
-        Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
-        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
-        startService(moveIntent);
-
-        try {
-            // There should be 3 operations: file creation, writing, then deletion (due to failed
-            // copy). Wait for 4, in case the CopyService also attempts to do extra stuff (like
-            // delete the src file). This should time out.
-            mResolver.waitForChanges(4);
-        } catch (TimeoutException e) {
-            // Success path
-            return;
-        } finally {
-            // Verify that the failed copy was cleaned up, and the src file wasn't removed.
-            assertDstFileCountEquals(0);
-            assertExists(SRC, srcPath);
-        }
-        // The asserts above didn't fail, but the CopyService did something unexpected.
-        fail("Extra file operations were detected");
-    }
-
-    public void testMoveDirectoryWithReadErrors() throws Exception {
-        String testContent[] = {
-                "The five boxing wizards jump quickly",
-                "The quick brown fox jumps over the lazy dog",
-                "Jackdaws love my big sphinx of quartz"
-        };
-        String srcDir = "/testdir";
-        String srcFiles[] = {
-                srcDir + "/test0.txt",
-                srcDir + "/test1.txt",
-                srcDir + "/test2.txt"
-        };
-        // Create test dir; put some files in it.
-        Uri testDir = mStorage.createFile(SRC, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
-                null);
-        mStorage.createFile(SRC, srcFiles[0], "text/plain", testContent[0].getBytes());
-        Uri errFile = mStorage
-                .createFile(SRC, srcFiles[1], "text/plain", testContent[1].getBytes());
-        mStorage.createFile(SRC, srcFiles[2], "text/plain", testContent[2].getBytes());
-
-        mStorage.simulateReadErrorsForFile(errFile);
-
-        Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
-        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
-        startService(moveIntent);
-
-        // - dst dir creation,
-        // - creation and writing of 2 files, removal of 2 src files
-        // - creation and writing of 1 file, then removal of that file (due to error)
-        mResolver.waitForChanges(10);
-
-        // Check that both the src and dst dirs exist. The src dir shouldn't have been removed,
-        // because it should contain the one errFile.
-        assertTrue(mStorage.getFile(SRC, srcDir).isDirectory());
-        assertTrue(mStorage.getFile(DST, srcDir).isDirectory());
-
-        // Check the content of the moved files.
-        MoreAsserts.assertEquals("Copied file contents differ", testContent[0].getBytes(),
-                readFile(DST, srcFiles[0]));
-        MoreAsserts.assertEquals("Copied file contents differ", testContent[2].getBytes(),
-                readFile(DST, srcFiles[2]));
-
-        // Check that the src files were removed.
-        assertDoesNotExist(SRC, srcFiles[0]);
-        assertDoesNotExist(SRC, srcFiles[2]);
-
-        // Check that the error file was not copied over.
-        assertDoesNotExist(DST, srcFiles[1]);
-        assertExists(SRC, srcFiles[1]);
-    }
-
-    /**
-     * Copies the given files to a pre-determined destination.
-     *
-     * @throws FileNotFoundException
-     */
-    private Intent createCopyIntent(List<Uri> srcs) throws FileNotFoundException {
-        final ArrayList<DocumentInfo> srcDocs = Lists.newArrayList();
-        for (Uri src : srcs) {
-            srcDocs.add(DocumentInfo.fromUri(mResolver, src));
-        }
-
-        final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, mRoots.get(1).documentId);
-        DocumentStack stack = new DocumentStack();
-        stack.push(DocumentInfo.fromUri(mResolver, dst));
-        final Intent copyIntent = new Intent(mContext, CopyService.class);
-        copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST, srcDocs);
-        copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
-
-        // startService(copyIntent);
-        return copyIntent;
-    }
-
-    /**
-     * Returns a count of the files in the given directory.
-     */
-    private void assertDstFileCountEquals(int expected) throws RemoteException {
-        final Uri queryUri = DocumentsContract.buildChildDocumentsUri(AUTHORITY,
-                mRoots.get(1).documentId);
-        Cursor c = null;
-        int count = 0;
-        try {
-            c = mClient.query(queryUri, null, null, null, null);
-            count = c.getCount();
-        } finally {
-            IoUtils.closeQuietly(c);
-        }
-        assertEquals("Incorrect file count after copy", expected, count);
-    }
-
-    private void assertExists(String rootId, String path) throws Exception {
-        assertNotNull("An expected file was not found: " + path + " on root " + rootId,
-                mStorage.getFile(rootId, path));
-    }
-
-    private void assertDoesNotExist(String rootId, String path) throws Exception {
-        assertNull("Unexpected file found: " + path + " on root " + rootId,
-                mStorage.getFile(rootId, path));
-    }
-
-    private byte[] readFile(String rootId, String path) throws Exception {
-        File file = mStorage.getFile(rootId, path);
-        byte[] buf = null;
-        assertNotNull(file);
-
-        FileInputStream in = null;
-        try {
-            in = new FileInputStream(file);
-            buf = Streams.readFully(in);
-        } finally {
-            IoUtils.closeQuietly(in);
-        }
-        return buf;
-    }
-
-    private void assertCopied(String path) throws Exception {
-        MoreAsserts.assertEquals("Copied file contents differ", readFile(SRC, path),
-                readFile(DST, path));
-    }
-
-    /**
-     * Sets up a ContextWrapper that substitutes a stub NotificationManager. This allows the test to
-     * listen for notification events, to gauge copy progress.
-     *
-     * @throws FileNotFoundException
-     */
-    private void setupTestContext() throws FileNotFoundException {
-        mSystemContext = getSystemContext();
-
-        // Set up the context with the test content resolver.
-        mResolver = new TestContentResolver();
-        mContext = new ContextWrapper(mSystemContext) {
-            @Override
-            public ContentResolver getContentResolver() {
-                return mResolver;
-            }
-        };
-        setContext(mContext);
-
-        // Create a local stub provider and add it to the content resolver.
-        ProviderInfo info = new ProviderInfo();
-        info.authority = AUTHORITY;
-        info.exported = true;
-        info.grantUriPermissions = true;
-        info.readPermission = android.Manifest.permission.MANAGE_DOCUMENTS;
-        info.writePermission = android.Manifest.permission.MANAGE_DOCUMENTS;
-
-        mStorage = new StubProvider();
-        mStorage.attachInfo(mContext, info);
-        mResolver.addProvider(AUTHORITY, mStorage);
-    }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
new file mode 100644
index 0000000..7abc99c
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import static com.android.documentsui.model.DocumentInfo.getCursorString;
+
+import android.content.ContentProviderClient;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+
+import com.android.documentsui.model.RootInfo;
+
+import libcore.io.IoUtils;
+
+/**
+ * Provides support for creation of documents in a test settings.
+ */
+public class DocumentsProviderHelper {
+
+    private final ContentProviderClient mClient;
+    private final String mAuthority;
+
+    public DocumentsProviderHelper(String authority, ContentProviderClient client) {
+        mClient = client;
+        mAuthority = authority;
+    }
+
+    public RootInfo getRoot(String id) throws RemoteException {
+        final Uri rootsUri = DocumentsContract.buildRootsUri(mAuthority);
+
+        Cursor cursor = null;
+        try {
+            cursor = mClient.query(rootsUri, null, null, null, null);
+            while (cursor.moveToNext()) {
+                if (id.equals(getCursorString(cursor, Root.COLUMN_ROOT_ID))) {
+                    return RootInfo.fromRootsCursor(mAuthority, cursor);
+                }
+            }
+            throw new IllegalArgumentException("Can't find matching root for id=" + id);
+        } catch (Exception e) {
+            throw new RuntimeException("Can't load root for id=" + id , e);
+        } finally {
+            IoUtils.closeQuietly(cursor);
+        }
+    }
+
+    public Uri createDocument(Uri parentUri, String mimeType, String name) {
+        if (name.contains("/")) {
+            throw new IllegalArgumentException("Name and mimetype probably interposed.");
+        }
+        try {
+            return DocumentsContract.createDocument(mClient, parentUri, mimeType, name);
+        } catch (RemoteException e) {
+            throw new RuntimeException("Couldn't create document: " + name + " with mimetype " + mimeType, e);
+        }
+    }
+
+    public Uri createFolder(Uri parentUri, String name) {
+        return createDocument(parentUri, Document.MIME_TYPE_DIR, name);
+    }
+
+    public Uri createDocument(RootInfo root, String mimeType, String name) {
+        Uri rootUri = DocumentsContract.buildDocumentUri(mAuthority, root.documentId);
+        return createDocument(rootUri, mimeType, name);
+    }
+
+    public Uri createFolder(RootInfo root, String name) {
+        return createDocument(root, Document.MIME_TYPE_DIR, name);
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 1f4b751..9060516 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -16,83 +16,158 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.StubProvider.DEFAULT_AUTHORITY;
+import static com.android.documentsui.StubProvider.ROOT_0_ID;
+import static com.android.documentsui.StubProvider.ROOT_1_ID;
+
+import android.app.Instrumentation;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.os.RemoteException;
 import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.Configurator;
 import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.Until;
 import android.test.InstrumentationTestCase;
+import android.util.Log;
 import android.view.MotionEvent;
 
-import java.util.concurrent.TimeoutException;
+import com.android.documentsui.model.RootInfo;
 
 public class FilesActivityUiTest extends InstrumentationTestCase {
 
+    private static final int TIMEOUT = 5000;
     private static final String TAG = "FilesActivityUiTest";
     private static final String TARGET_PKG = "com.android.documentsui";
     private static final String LAUNCHER_PKG = "com.android.launcher";
-    private static final int ONE_SECOND = 1000;
-    private static final int FIVE_SECONDS = 5 * ONE_SECOND;
 
-    private ActionBar mBar;
+    private UiBot mBot;
     private UiDevice mDevice;
     private Context mContext;
+    private ContentResolver mResolver;
+    private DocumentsProviderHelper mDocsHelper;
+    private ContentProviderClient mClient;
+    private RootInfo mRoot_0;
+    private RootInfo mRoot_1;
 
-    public void setUp() throws TimeoutException {
+    public void setUp() throws Exception {
         // Initialize UiDevice instance.
-        mDevice = UiDevice.getInstance(getInstrumentation());
+        Instrumentation instrumentation = getInstrumentation();
+
+        mDevice = UiDevice.getInstance(instrumentation);
 
         Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
 
         // Start from the home screen.
         mDevice.pressHome();
-        mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), FIVE_SECONDS);
+        mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), TIMEOUT);
+
+        // NOTE: Must be the "target" context, else security checks in content provider will fail.
+        mContext = instrumentation.getTargetContext();
+        mResolver = mContext.getContentResolver();
+
+        mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
+        mDocsHelper = new DocumentsProviderHelper(DEFAULT_AUTHORITY, mClient);
 
         // Launch app.
-        mContext = getInstrumentation().getContext();
         Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(TARGET_PKG);
         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
         mContext.startActivity(intent);
 
         // Wait for the app to appear.
-        mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), FIVE_SECONDS);
+        mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
         mDevice.waitForIdle();
 
-        mBar = new ActionBar();
+        mBot = new UiBot(mDevice, TIMEOUT);
+
+        resetStorage();  // Just incase a test failed and tearDown didn't happen.
     }
 
-    public void testSwitchMode() throws Exception {
-        UiObject2 mode = mBar.gridMode(100);
-        if (mode != null) {
-            mode.click();
-            assertNotNull(mBar.listMode(ONE_SECOND));
-        } else {
-            mBar.listMode(100).click();
-            assertNotNull(mBar.gridMode(ONE_SECOND));
-        }
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        Log.d(TAG, "Resetting storage from setUp");
+        resetStorage();
+        mClient.release();
     }
 
-    private class ActionBar {
+    private void resetStorage() throws RemoteException {
+        mClient.call("clear", null, null);
+        // TODO: Would be nice to have an event to wait on here.
+        mDevice.waitForIdle();
+    }
 
-        public UiObject2 gridMode(int timeout) {
-            // Note that we're using By.desc rather than By.res, because of b/25285770
-            BySelector selector = By.desc("Grid view");
-            if (timeout > 0) {
-                mDevice.wait(Until.findObject(selector), timeout);
-            }
-            return mDevice.findObject(selector);
-        }
+    private void initTestFiles() throws RemoteException {
+        mRoot_0 = mDocsHelper.getRoot(ROOT_0_ID);
+        mRoot_1 = mDocsHelper.getRoot(ROOT_1_ID);
 
-        public UiObject2 listMode(int timeout) {
-            // Note that we're using By.desc rather than By.res, because of b/25285770
-            BySelector selector = By.desc("List view");
-            if (timeout > 0) {
-                mDevice.wait(Until.findObject(selector), timeout);
-            }
-            return mDevice.findObject(selector);
-        }
+        mDocsHelper.createDocument(mRoot_0, "text/plain", "file0.log");
+        mDocsHelper.createDocument(mRoot_0, "image/png", "file1.png");
+        mDocsHelper.createDocument(mRoot_0, "text/csv", "file2.csv");
+
+        mDocsHelper.createDocument(mRoot_1, "text/plain", "anotherFile0.log");
+        mDocsHelper.createDocument(mRoot_1, "text/plain", "poodles.text");
+    }
+
+    public void testRootsListed() throws Exception {
+        initTestFiles();
+
+        mBot.openRoot(ROOT_0_ID);
+
+        // Should also have Drive, but that requires pre-configuration of devices
+        // We omit for now.
+        mBot.assertHasRoots(
+                "Images",
+                "Videos",
+                "Audio",
+                "Downloads",
+                ROOT_0_ID,
+                ROOT_1_ID);
+    }
+
+    public void testFilesListed() throws Exception {
+        initTestFiles();
+
+        mBot.openRoot(ROOT_0_ID);
+        mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
+    }
+
+    public void testFilesList_LiveUpdate() throws Exception {
+        initTestFiles();
+
+        mBot.openRoot(ROOT_0_ID);
+        mDocsHelper.createDocument(mRoot_0, "yummers/sandwich", "Ham & Cheese.sandwich");
+        mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
+    }
+
+    public void testDeleteDocument() throws Exception {
+        initTestFiles();
+
+        mBot.openRoot(ROOT_0_ID);
+
+        mBot.clickDocument("file1.png");
+        mDevice.waitForIdle();
+        mBot.menuDelete().click();
+
+        mBot.waitForDeleteSnackbar();
+        assertFalse(mBot.hasDocuments("file1.png"));
+
+        mBot.waitForDeleteSnackbarGone();
+        assertFalse(mBot.hasDocuments("file1.png"));
+
+        // Now delete from another root.
+        mBot.openRoot(ROOT_1_ID);
+
+        mBot.clickDocument("poodles.text");
+        mDevice.waitForIdle();
+        mBot.menuDelete().click();
+
+        mBot.waitForDeleteSnackbar();
+        assertFalse(mBot.hasDocuments("poodles.text"));
+
+        mBot.waitForDeleteSnackbarGone();
+        assertFalse(mBot.hasDocuments("poodles.text"));
     }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 6a2e03a..2d42ddc 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -50,12 +50,17 @@
 import java.util.Map;
 
 public class StubProvider extends DocumentsProvider {
+
+    public static final String DEFAULT_AUTHORITY = "com.android.documentsui.stubprovider";
+    public static final String ROOT_0_ID = "TEST_ROOT_0";
+    public static final String ROOT_1_ID = "TEST_ROOT_1";
+
+    private static final String TAG = "StubProvider";
     private static final String EXTRA_SIZE = "com.android.documentsui.stubprovider.SIZE";
     private static final String EXTRA_ROOT = "com.android.documentsui.stubprovider.ROOT";
     private static final String STORAGE_SIZE_KEY = "documentsui.stubprovider.size";
-    private static int DEFAULT_SIZE = 1024 * 1024; // 1 MB.
-    private static final String TAG = "StubProvider";
-    private static final String MY_ROOT_ID = "sd0";
+    private static int DEFAULT_ROOT_SIZE = 1024 * 1024 * 100; // 100 MB.
+
     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
             Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
             Root.COLUMN_AVAILABLE_BYTES
@@ -65,11 +70,12 @@
             Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
     };
 
-    private HashMap<String, StubDocument> mStorage = new HashMap<String, StubDocument>();
-    private Object mWriteLock = new Object();
-    private String mAuthority;
+    private final Map<String, StubDocument> mStorage = new HashMap<>();
+    private final Map<String, RootInfo> mRoots = new HashMap<>();
+    private final Object mWriteLock = new Object();
+
+    private String mAuthority = DEFAULT_AUTHORITY;
     private SharedPreferences mPrefs;
-    private Map<String, RootInfo> mRoots;
     private String mSimulateReadErrors;
 
     @Override
@@ -86,20 +92,18 @@
 
     @VisibleForTesting
     public void clearCacheAndBuildRoots() {
-        final File cacheDir = getContext().getCacheDir();
-        removeRecursively(cacheDir);
+        Log.d(TAG, "Resetting storage.");
+        removeChildrenRecursively(getContext().getCacheDir());
         mStorage.clear();
 
         mPrefs = getContext().getSharedPreferences(
                 "com.android.documentsui.stubprovider.preferences", Context.MODE_PRIVATE);
         Collection<String> rootIds = mPrefs.getStringSet("roots", null);
         if (rootIds == null) {
-            rootIds = Arrays.asList(new String[] {
-                    "sd0", "sd1"
-            });
+            rootIds = Arrays.asList(new String[] { ROOT_0_ID, ROOT_1_ID });
         }
-        // Create new roots.
-        mRoots = new HashMap<>();
+
+        mRoots.clear();
         for (String rootId : rootIds) {
             final RootInfo rootInfo = new RootInfo(rootId, getSize(rootId));
             mRoots.put(rootId, rootInfo);
@@ -111,7 +115,7 @@
      */
     private long getSize(String rootId) {
         final String key = STORAGE_SIZE_KEY + "." + rootId;
-        return mPrefs.getLong(key, DEFAULT_SIZE);
+        return mPrefs.getLong(key, DEFAULT_ROOT_SIZE);
     }
 
     @Override
@@ -125,7 +129,7 @@
             row.add(Root.COLUMN_ROOT_ID, id);
             row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
             row.add(Root.COLUMN_TITLE, id);
-            row.add(Root.COLUMN_DOCUMENT_ID, info.rootDocument.documentId);
+            row.add(Root.COLUMN_DOCUMENT_ID, info.document.documentId);
             row.add(Root.COLUMN_AVAILABLE_BYTES, info.getRemainingCapacity());
         }
         return result;
@@ -152,28 +156,48 @@
     }
 
     @Override
-    public String createDocument(String parentDocumentId, String mimeType, String displayName)
+    public String createDocument(String parentId, String mimeType, String displayName)
             throws FileNotFoundException {
-        final StubDocument parentDocument = mStorage.get(parentDocumentId);
-        if (parentDocument == null || !parentDocument.file.isDirectory()) {
-            throw new FileNotFoundException();
+
+        final StubDocument parent = mStorage.get(parentId);
+        if (parent == null) {
+            throw new IllegalArgumentException(
+                    "Can't create file " + displayName + " in null parent.");
         }
-        final File file = new File(parentDocument.file, displayName);
-        if (mimeType.equals(Document.MIME_TYPE_DIR)) {
-            if (!file.mkdirs()) {
-                throw new FileNotFoundException();
-            }
-        } else {
-            try {
-                if (!file.createNewFile()) {
-                    throw new IllegalStateException("The file " + file.getPath() + " already exists");
-                }
-            } catch (IOException e) {
-                throw new FileNotFoundException();
-            }
+        if (!parent.file.isDirectory()) {
+            throw new IllegalArgumentException(
+                    "Can't create file " + displayName + " inside non-directory parent "
+                    + parent.file.getName());
         }
 
-        final StubDocument document = new StubDocument(file, mimeType, parentDocument);
+        final File file = new File(parent.file, displayName);
+        if (file.exists()) {
+            throw new FileNotFoundException(
+                    "Duplicate file names not supported for " + file);
+        }
+
+        if (mimeType.equals(Document.MIME_TYPE_DIR)) {
+            if (!file.mkdirs()) {
+                throw new FileNotFoundException(
+                        "Failed to create directory(s): " + file);
+            }
+            Log.i(TAG, "Created new directory: " + file);
+        } else {
+            boolean created = false;
+            try {
+                created = file.createNewFile();
+            } catch (IOException e) {
+                // We'll throw an FNF exception later :)
+                Log.e(TAG, "createnewFile operation failed for file: " + file, e);
+            }
+            if (!created) {
+                throw new FileNotFoundException(
+                        "createNewFile operation failed for: " + file);
+            }
+            Log.i(TAG, "Created new file: " + file);
+        }
+
+        final StubDocument document = new StubDocument(file, mimeType, parent);
         Log.d(TAG, "Created document " + document.documentId);
         notifyParentChanged(document.parentId);
         getContext().getContentResolver().notifyChange(
@@ -349,7 +373,7 @@
 
     private void configure(String arg, Bundle extras) {
         Log.d(TAG, "Configure " + arg);
-        String rootName = extras.getString(EXTRA_ROOT, MY_ROOT_ID);
+        String rootName = extras.getString(EXTRA_ROOT, ROOT_0_ID);
         long rootSize = extras.getLong(EXTRA_SIZE, 1) * 1024 * 1024;
         setSize(rootName, rootSize);
     }
@@ -379,10 +403,10 @@
         row.add(Document.COLUMN_LAST_MODIFIED, document.file.lastModified());
     }
 
-    private void removeRecursively(File file) {
+    private void removeChildrenRecursively(File file) {
         for (File childFile : file.listFiles()) {
             if (childFile.isDirectory()) {
-                removeRecursively(childFile);
+                removeChildrenRecursively(childFile);
             }
             childFile.delete();
         }
@@ -411,8 +435,8 @@
     @VisibleForTesting
     public Uri createFile(String rootId, String path, String mimeType, byte[] content)
             throws FileNotFoundException, IOException {
-        Log.d(TAG, "Creating file " + rootId + ":" + path);
-        StubDocument root = mRoots.get(rootId).rootDocument;
+        Log.d(TAG, "Creating test file " + rootId + ":" + path);
+        StubDocument root = mRoots.get(rootId).document;
         if (root == null) {
             throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
         }
@@ -445,7 +469,7 @@
 
     @VisibleForTesting
     public File getFile(String rootId, String path) throws FileNotFoundException {
-        StubDocument root = mRoots.get(rootId).rootDocument;
+        StubDocument root = mRoots.get(rootId).document;
         if (root == null) {
             throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
         }
@@ -461,7 +485,7 @@
 
     final class RootInfo {
         public final String name;
-        public final StubDocument rootDocument;
+        public final StubDocument document;
         public long capacity;
         public long size;
 
@@ -469,9 +493,11 @@
             this.name = name;
             this.capacity = 1024 * 1024;
             // Make a subdir in the cache dir for each root.
-            File rootDir = new File(getContext().getCacheDir(), name);
-            rootDir.mkdir();
-            this.rootDocument = new StubDocument(rootDir, Document.MIME_TYPE_DIR, this);
+            File file = new File(getContext().getCacheDir(), name);
+            if (file.mkdir()) {
+                Log.i(TAG, "Created new root directory @ " + file.getPath());
+            }
+            this.document = new StubDocument(file, Document.MIME_TYPE_DIR, this);
             this.capacity = capacity;
             this.size = 0;
         }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
new file mode 100644
index 0000000..5c09794
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * A test helper class that provides support for controlling DocumentsUI activities
+ * programmatically, and making assertions against the state of the UI.
+ */
+class UiBot {
+
+    private static final String TAG = "UiBot";
+
+    private static final BySelector SNACK_DELETE =
+            By.desc(Pattern.compile("^Deleting [0-9]+ file.+"));
+
+    private UiDevice mDevice;
+    private int mTimeout;
+
+    public UiBot(UiDevice device, int timeout) {
+        mDevice = device;
+        mTimeout = timeout;
+    }
+
+    UiObject findRoot(String label) throws UiObjectNotFoundException {
+        final UiSelector rootsList = new UiSelector().resourceId(
+                "com.android.documentsui:id/container_roots").childSelector(
+                new UiSelector().resourceId("android:id/list"));
+
+        // We might need to expand drawer if not visible
+        if (!new UiObject(rootsList).waitForExists(mTimeout)) {
+            Log.d(TAG, "Failed to find roots list; trying to expand");
+            final UiSelector hamburger = new UiSelector().resourceId(
+                    "com.android.documentsui:id/toolbar").childSelector(
+                    new UiSelector().className("android.widget.ImageButton").clickable(true));
+            new UiObject(hamburger).click();
+        }
+
+        // Wait for the first list item to appear
+        new UiObject(rootsList.childSelector(new UiSelector())).waitForExists(mTimeout);
+
+        // Now scroll around to find our item
+        new UiScrollable(rootsList).scrollIntoView(new UiSelector().text(label));
+        return new UiObject(rootsList.childSelector(new UiSelector().text(label)));
+    }
+
+    void openRoot(String label) throws UiObjectNotFoundException {
+        findRoot(label).click();
+        mDevice.waitForIdle();
+    }
+
+    void assertHasRoots(String... labels) throws UiObjectNotFoundException {
+        List<String> missing = new ArrayList<>();
+        for (String label : labels) {
+            if (!findRoot(label).exists()) {
+                missing.add(label);
+            }
+        }
+        if (!missing.isEmpty()) {
+            Assert.fail(
+                    "Expected roots " + Arrays.asList(labels) + ", but missing " + missing);
+        }
+    }
+
+    UiObject findDocument(String label) throws UiObjectNotFoundException {
+        final UiSelector docList = new UiSelector().resourceId(
+                "com.android.documentsui:id/container_directory").childSelector(
+                        new UiSelector().resourceId("com.android.documentsui:id/list"));
+
+        // Wait for the first list item to appear
+        new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout);
+
+        // new UiScrollable(docList).scrollIntoView(new UiSelector().text(label));
+        return mDevice.findObject(docList.childSelector(new UiSelector().text(label)));
+    }
+
+    boolean hasDocuments(String... labels) throws UiObjectNotFoundException {
+        for (String label : labels) {
+            if (!findDocument(label).exists()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    void assertHasDocuments(String... labels) throws UiObjectNotFoundException {
+        List<String> missing = new ArrayList<>();
+        for (String label : labels) {
+            if (!findDocument(label).exists()) {
+                missing.add(label);
+            }
+        }
+        if (!missing.isEmpty()) {
+            Assert.fail(
+                    "Expected documents " + Arrays.asList(labels) + ", but missing " + missing);
+        }
+    }
+
+    void clickDocument(String label) throws UiObjectNotFoundException {
+        findDocument(label).click();
+    }
+
+    void waitForDeleteSnackbar() {
+        mDevice.wait(Until.findObject(SNACK_DELETE), mTimeout);
+    }
+
+    void waitForDeleteSnackbarGone() {
+        // wait a little longer for snackbar to go away, as it disappears after a timeout.
+        mDevice.wait(Until.gone(SNACK_DELETE), mTimeout * 2);
+    }
+
+    void switchViewMode() {
+        UiObject2 mode = menuGridMode();
+        if (mode != null) {
+            mode.click();
+        } else {
+            menuListMode().click();
+        }
+    }
+
+    UiObject2 menuGridMode() {
+        // Note that we're using By.desc rather than By.res, because of b/25285770
+        return find(By.desc("Grid view"));
+    }
+
+    UiObject2 menuListMode() {
+        // Note that we're using By.desc rather than By.res, because of b/25285770
+        return find(By.desc("List view"));
+    }
+
+    UiObject2 menuDelete() {
+        return find(By.res("com.android.documentsui:id/menu_delete"));
+    }
+
+    private UiObject2 find(BySelector selector) {
+        mDevice.wait(Until.findObject(selector), mTimeout);
+        return mDevice.findObject(selector);
+    }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index c44a7960..14eb084 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -231,4 +231,113 @@
     <!-- Launch defaults preference summary with none set [CHAR LIMIT=40] -->
     <string name="launch_defaults_none">No defaults set</string>
 
+    <!-- Text-To-Speech (TTS) settings --><skip />
+    <!-- Name of the TTS package as listed by the package manager. -->
+    <string name="tts_settings">Text-to-speech settings</string>
+    <!-- TTS option item name in the main settings screen -->
+    <string name="tts_settings_title">Text-to-speech output</string>
+    <!-- On main TTS Settings screen, in default settings section, setting default speech rate for synthesized voice -->
+    <string name="tts_default_rate_title">Speech rate</string>
+    <!-- On main TTS Settings screen, summary for default speech rate for synthesized voice -->
+    <string name="tts_default_rate_summary">Speed at which the text is spoken</string>
+    <!-- On main TTS Settings screen, in default settings section, setting default language for synthesized voice -->
+    <string name="tts_default_lang_title">Language</string>
+    <!-- Entry in the TTS engine language/locale picker, when selected will try to default to the system language [CHAR LIMIT=50] -->
+    <string name="tts_lang_use_system">Use system language</string>
+    <!-- On main TTS Settings screen, language summary if it can't default to system language [CHAR LIMIT=50] -->
+    <string name="tts_lang_not_selected">Language not selected</string>
+    <!-- On main TTS Settings screen, summary for default language for synthesized voice -->
+    <string name="tts_default_lang_summary">Sets the language-specific voice for the spoken text</string>
+    <!-- On main TTS Settings screen, triggers playback of an example of speech synthesis -->
+    <string name="tts_play_example_title">Listen to an example</string>
+    <!-- On main TTS Settings screen, summary for triggering playback of an example of speech synthesis -->
+    <string name="tts_play_example_summary">Play a short demonstration of speech synthesis</string>
+    <!-- On main TTS Settings screen, click to install required speech synthesis data -->
+    <string name="tts_install_data_title">Install voice data</string>
+    <!-- On main TTS Settings screen, summary for click to install required speech synthesis data -->
+    <string name="tts_install_data_summary">Install the voice data required for speech synthesis</string>
+    <!-- Warning message about security implications of enabling a TTS engine, displayed as a dialog
+         message when the user selects to enable an engine. -->
+    <string name="tts_engine_security_warning">This speech synthesis engine may be able to collect
+        all the text that will be spoken, including personal data like passwords and credit
+        card numbers. It comes from the <xliff:g id="tts_plugin_engine_name">%s</xliff:g> engine.
+        Enable the use of this speech synthesis engine?</string>
+    <!-- Warning message about required internet conectivity for TTS synthesis, displayed as a dialog
+         message when the user selects to play an example for network only locale and there's no internet connectivity. -->
+    <string name="tts_engine_network_required">This language requires a working network connection for text-to-speech output.</string>
+    <!-- Text spoken by the TTS engine as an example if the engine doesn't provide sample text [CHAR LIMIT=100] -->
+    <string name="tts_default_sample_string">This is an example of speech synthesis</string>
+    <!-- On main TTS Settings screen, title of a field explaining current TTS engine status for
+         current default language [CHAR LIMIT=50] -->
+    <string name="tts_status_title">Default language status</string>
+    <!-- On main TTS Settings screen, current TTS engine status for the current default language,
+         selected language is fully supported by the engine [CHAR LIMIT=150]-->
+    <string name="tts_status_ok"><xliff:g id="locale" example="English (United States)">%1$s</xliff:g> is fully supported</string>
+    <!-- On main TTS Settings screen, current TTS engine status for the current default language,
+         selected language is supported by the engine only if there's a working network connection [CHAR LIMIT=150] -->
+    <string name="tts_status_requires_network"><xliff:g id="locale" example="English (United States)">%1$s</xliff:g> requires network connection</string>
+    <!-- On main TTS Settings screen, current TTS engine status for the current default language,
+         selected language is not supported by the engine [CHAR LIMIT=150] -->
+    <string name="tts_status_not_supported"><xliff:g id="locale" example="English (United States)">%1$s</xliff:g> is not supported</string>
+    <!-- On main TTS Settings screen, current TTS engine status for the current default language,
+         tts engine is queried for status [CHAR LIMIT=150] -->
+    <string name="tts_status_checking">Checking&#8230;</string>
+    <!-- Title for a preference in the main TTS settings screen, which
+         launches the settings screen for a given TTS engine when clicked
+         [CHAR LIMIT=30]-->
+    <string name="tts_engine_settings_title">Settings for <xliff:g id="tts_engine_name">%s</xliff:g></string>
+    <!-- [CHAR LIMIT=150] Text for screen readers / accessibility programs for
+         the image that launches the TTS engine settings when clicked. -->
+    <string name="tts_engine_settings_button">Launch engine settings</string>
+    <!-- [CHAR LIMIT=50] The text for the settings section that users to set a
+         preferred text to speech engine -->
+    <string name="tts_engine_preference_section_title">Preferred engine</string>
+    <!-- [CHAR LIMIT=50] Title of the settings section that displays general preferences
+         that are applicable to all engines, such as the speech rate -->
+    <string name="tts_general_section_title">General</string>
+
+    <!-- Default speech rate choices -->
+    <string-array name="tts_rate_entries">
+        <item>Very slow</item>
+        <item>Slow</item>
+        <item>Normal</item>
+        <item>Fast</item>
+        <item>Faster</item>
+        <item>Very fast</item>
+        <item>Rapid</item>
+        <item>Very rapid</item>
+        <item>Fastest</item>
+    </string-array>
+    <!-- Do not translate. -->
+    <string-array name="tts_rate_values">
+        <item>60</item>
+        <item>80</item>
+        <item>100</item>
+        <item>150</item>
+        <item>200</item>
+        <item>250</item>
+        <item>300</item>
+        <item>350</item>
+        <item>400</item>
+    </string-array>
+
+    <!-- Do not translate. -->
+    <string-array name="tts_demo_strings" translatable="false">
+        <item>This is an example of speech synthesis in English.</item>
+        <item>Voici un échantillon de synthèse vocale en français.</item>
+        <item>Dies ist ein Beispiel für Sprachsynthese in Deutsch.</item>
+        <item>Questo è un esempio di sintesi vocale in italiano.</item>
+        <item>Este es un ejemplo de síntesis de voz en español.</item>
+        <item>이것은 한국어 음성 합성의 예입니다.</item>
+    </string-array>
+    <!-- Do not translate. -->
+    <string-array name="tts_demo_string_langs" translatable="false">
+        <item>eng</item>
+        <item>fra</item>
+        <item>deu</item>
+        <item>ita</item>
+        <item>spa</item>
+        <item>kor</item>
+    </string-array>
+
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
index cf17ea5..99bd4ad 100644
--- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -16,15 +16,20 @@
 
 package com.android.settingslib.accessibility;
 
+import android.accessibilityservice.AccessibilityServiceInfo;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.ArraySet;
+import android.view.accessibility.AccessibilityManager;
 
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 
@@ -45,7 +50,7 @@
             return Collections.emptySet();
         }
 
-        final Set<ComponentName> enabledServices = new HashSet<ComponentName>();
+        final Set<ComponentName> enabledServices = new HashSet<>();
         final TextUtils.SimpleStringSplitter colonSplitter = sStringColonSplitter;
         colonSplitter.setString(enabledServicesSetting);
 
@@ -71,4 +76,79 @@
         final Context langContext = context.createConfigurationContext(config);
         return langContext.getText(resId);
     }
+
+    public static void setAccessibilityServiceState(Context context, ComponentName toggledService,
+            boolean enabled) {
+        // Parse the enabled services.
+        Set<ComponentName> enabledServices = AccessibilityUtils.getEnabledServicesFromSettings(
+                context);
+
+        if (enabledServices.isEmpty()) {
+            enabledServices = new ArraySet<>(1);
+        }
+
+        // Determine enabled services and accessibility state.
+        boolean accessibilityEnabled = false;
+        if (enabled) {
+            enabledServices.add(toggledService);
+            // Enabling at least one service enables accessibility.
+            accessibilityEnabled = true;
+        } else {
+            enabledServices.remove(toggledService);
+            // Check how many enabled and installed services are present.
+            Set<ComponentName> installedServices = getInstalledServices(context);
+            for (ComponentName enabledService : enabledServices) {
+                if (installedServices.contains(enabledService)) {
+                    // Disabling the last service disables accessibility.
+                    accessibilityEnabled = true;
+                    break;
+                }
+            }
+        }
+
+        // Update the enabled services setting.
+        StringBuilder enabledServicesBuilder = new StringBuilder();
+        // Keep the enabled services even if they are not installed since we
+        // have no way to know whether the application restore process has
+        // completed. In general the system should be responsible for the
+        // clean up not settings.
+        for (ComponentName enabledService : enabledServices) {
+            enabledServicesBuilder.append(enabledService.flattenToString());
+            enabledServicesBuilder.append(
+                    AccessibilityUtils.ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
+        }
+        final int enabledServicesBuilderLength = enabledServicesBuilder.length();
+        if (enabledServicesBuilderLength > 0) {
+            enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
+        }
+        Settings.Secure.putString(context.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                enabledServicesBuilder.toString());
+
+        // Update accessibility enabled.
+        Settings.Secure.putInt(context.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
+    }
+
+    private static Set<ComponentName> getInstalledServices(Context context) {
+        final Set<ComponentName> installedServices = new HashSet<>();
+        installedServices.clear();
+
+        final List<AccessibilityServiceInfo> installedServiceInfos =
+                AccessibilityManager.getInstance(context)
+                        .getInstalledAccessibilityServiceList();
+        if (installedServiceInfos == null) {
+            return installedServices;
+        }
+
+        for (final AccessibilityServiceInfo info : installedServiceInfos) {
+            final ResolveInfo resolveInfo = info.getResolveInfo();
+            final ComponentName installedService = new ComponentName(
+                    resolveInfo.serviceInfo.packageName,
+                    resolveInfo.serviceInfo.name);
+            installedServices.add(installedService);
+        }
+        return installedServices;
+    }
+
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index d7dfdf8..e28b2e5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -258,6 +258,9 @@
         mScanId++;
         final List<ScanResult> newResults = mWifiManager.getScanResults();
         for (ScanResult newResult : newResults) {
+            if (newResult.SSID == null || newResult.SSID.isEmpty()) {
+                continue;
+            }
             mScanResultCache.put(newResult.BSSID, newResult);
             mSeenBssids.put(newResult.BSSID, mScanId);
         }
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index 0d1ad19..e90a3b5 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -136,9 +136,10 @@
         // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
         // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
         // create the ClipData object with the attachments URIs.
-        intent.putExtra(Intent.EXTRA_TEXT, SystemProperties.get("ro.build.description"));
-        final ClipData clipData = new ClipData(
-                null, new String[] { mimeType },
+        String messageBody = String.format("Build info: %s\nSerial number:%s",
+                SystemProperties.get("ro.build.description"), SystemProperties.get("ro.serialno"));
+        intent.putExtra(Intent.EXTRA_TEXT, messageBody);
+        final ClipData clipData = new ClipData(null, new String[] { mimeType },
                 new ClipData.Item(null, null, null, bugreportUri));
         clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
         intent.setClipData(clipData);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 794e900..049754e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -19,12 +19,14 @@
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Handler;
 import android.os.Message;
+import android.provider.Settings;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -34,6 +36,7 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
@@ -620,4 +623,9 @@
         int getOffsetTop(TileRecord tile);
         void updateResources();
     }
+
+    public static boolean isTheNewQS(Context context) {
+        return Settings.Secure.getIntForUser(context.getContentResolver(), QS_THE_NEW_QS,
+                ActivityManager.getCurrentUser(), 0) != 0;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 61695b2..48b74a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -28,6 +28,7 @@
 import com.android.systemui.R;
 import com.android.systemui.qs.QSDetailItems;
 import com.android.systemui.qs.QSDetailItems.Item;
+import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
@@ -93,7 +94,7 @@
     @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
         state.visible = !mKeyguard.isSecure() || !mKeyguard.isShowing()
-                || mKeyguard.canSkipBouncer();
+                || mKeyguard.canSkipBouncer() || QSPanel.isTheNewQS(mContext);
         state.label = mContext.getString(R.string.quick_settings_cast_title);
         state.value = false;
         state.autoMirrorDrawable = false;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index c6fc6ff..2f9a496 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -21,6 +21,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
+import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.SecureSetting;
 import com.android.systemui.qs.UsageTracker;
@@ -110,7 +111,7 @@
     protected void handleUpdateState(BooleanState state, Object arg) {
         final int value = arg instanceof Integer ? (Integer) arg : mSetting.getValue();
         final boolean enabled = value != 0;
-        state.visible = enabled || mUsageTracker.isRecentlyUsed();
+        state.visible = enabled || mUsageTracker.isRecentlyUsed() || QSPanel.isTheNewQS(mContext);
         state.value = enabled;
         state.label = mContext.getString(R.string.quick_settings_inversion_label);
         state.icon = enabled ? mEnable : mDisable;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 7b83e6a..79084ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -23,6 +23,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
+import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.UsageTracker;
 import com.android.systemui.statusbar.policy.HotspotController;
@@ -88,7 +89,8 @@
 
     @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
-        state.visible = mController.isHotspotSupported() && mUsageTracker.isRecentlyUsed();
+        state.visible = (mController.isHotspotSupported() && mUsageTracker.isRecentlyUsed())
+                || QSPanel.isTheNewQS(mContext);
         state.label = mContext.getString(R.string.quick_settings_hotspot_label);
 
         if (arg instanceof Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index e6fade4..0e2672c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -18,6 +18,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
+import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.LocationController;
@@ -73,7 +74,7 @@
         // Work around for bug 15916487: don't show location tile on top of lock screen. After the
         // bug is fixed, this should be reverted to only hiding it on secure lock screens:
         // state.visible = !(mKeyguard.isSecure() && mKeyguard.isShowing());
-        state.visible = !mKeyguard.isShowing();
+        state.visible = !mKeyguard.isShowing() || QSPanel.isTheNewQS(mContext);
         state.value = locationEnabled;
         if (locationEnabled) {
             state.icon = mEnable;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 4d40cb7..a58bc58 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -21,9 +21,11 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.res.Configuration;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.Display;
@@ -52,10 +54,21 @@
     public final static int EVENT_BUS_PRIORITY = 1;
     public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000;
 
+    // Purely for experimentation
+    private final static String RECENTS_OVERRIDE_SYSPROP_KEY = "persist.recents_override_pkg";
+    private final static String ACTION_SHOW_RECENTS = "com.android.systemui.recents.ACTION_SHOW";
+    private final static String ACTION_HIDE_RECENTS = "com.android.systemui.recents.ACTION_HIDE";
+    private final static String ACTION_TOGGLE_RECENTS = "com.android.systemui.recents.ACTION_TOGGLE";
+
     private static SystemServicesProxy sSystemServicesProxy;
     private static RecentsTaskLoader sTaskLoader;
     private static RecentsConfiguration sConfiguration;
 
+    // For experiments only, allows another package to handle recents if it is defined in the system
+    // properties.  This is limited to show/toggle/hide, and does not tie into the ActivityManager,
+    // and does not reside in the home stack.
+    private String mOverrideRecentsPackageName;
+
     private Handler mHandler;
     private RecentsImpl mImpl;
 
@@ -142,6 +155,14 @@
         mHandler = new Handler();
         mImpl = new RecentsImpl(mContext);
 
+        // Check if there is a recents override package
+        if ("userdebug".equals(Build.TYPE) || "eng".equals(Build.TYPE)) {
+            String cnStr = SystemProperties.get(RECENTS_OVERRIDE_SYSPROP_KEY);
+            if (!cnStr.isEmpty()) {
+                mOverrideRecentsPackageName = cnStr;
+            }
+        }
+
         // Register with the event bus
         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
         EventBus.getDefault().register(sTaskLoader, EVENT_BUS_PRIORITY);
@@ -172,6 +193,10 @@
      */
     @Override
     public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
+        if (proxyToOverridePackage(ACTION_SHOW_RECENTS)) {
+            return;
+        }
+
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
             mImpl.showRecents(triggeredFromAltTab);
@@ -197,6 +222,10 @@
      */
     @Override
     public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+        if (proxyToOverridePackage(ACTION_HIDE_RECENTS)) {
+            return;
+        }
+
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
             mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
@@ -222,6 +251,10 @@
      */
     @Override
     public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
+        if (proxyToOverridePackage(ACTION_TOGGLE_RECENTS)) {
+            return;
+        }
+
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
             mImpl.toggleRecents();
@@ -421,4 +454,19 @@
         }
         mOnConnectRunnables.clear();
     }
+
+    /**
+     * Attempts to proxy the following action to the override recents package.
+     * @return whether the proxying was successful
+     */
+    private boolean proxyToOverridePackage(String action) {
+        if (mOverrideRecentsPackageName != null) {
+            Intent intent = new Intent(action);
+            intent.setPackage(mOverrideRecentsPackageName);
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            mContext.sendBroadcast(intent);
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 4d575bc..b5a1a93 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -517,6 +517,15 @@
                 boolean hasRepKeyTimeElapsed = (SystemClock.elapsedRealtime() -
                         mLastTabKeyEventTime) > altTabKeyDelay;
                 if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
+                    // As we iterate to the next/previous task, cancel any current/lagging window
+                    // transition animations
+                    RecentsConfiguration config = Recents.getConfiguration();
+                    RecentsActivityLaunchState launchState = config.getLaunchState();
+                    if (launchState.launchedToTaskId != -1) {
+                        SystemServicesProxy ssp = Recents.getSystemServices();
+                        ssp.cancelWindowTransition(launchState.launchedToTaskId);
+                    }
+
                     // Focus the next task in the stack
                     final boolean backward = event.isShiftPressed();
                     if (backward) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 243c0e1..0e11f02 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.recents;
 
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
index 28299d3..d415845 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
@@ -33,8 +33,8 @@
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.views.RecentsView;
 
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 
 /**
  * A helper for the dialogs that show when task debugging is on.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
index ea6821f..515c3bd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
@@ -189,6 +189,7 @@
     public float computePOffsetForScaledHeight(int height, Rect bounds) {
         int top = bounds.top;
         int bottom = bounds.bottom;
+        height = Math.min(height, bottom - top);
 
         if (bounds.height() == 0) {
             return 0;
@@ -231,6 +232,7 @@
     public float computePOffsetForHeight(int height, Rect bounds) {
         int top = bounds.top;
         int bottom = bounds.bottom;
+        height = Math.min(height, bottom - top);
 
         if (bounds.height() == 0) {
             return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 141562c..2fe5e98 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -57,6 +57,7 @@
 import android.util.Pair;
 import android.view.Display;
 import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import com.android.internal.app.AssistUtils;
 import com.android.internal.os.BackgroundThread;
@@ -71,8 +72,10 @@
 import java.util.List;
 import java.util.Random;
 
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 
 /**
  * Acts as a shim around the real system services that we need to access data from, and provides
@@ -232,6 +235,15 @@
         return null;
     }
 
+    /**
+     * Returns whether this device has freeform workspaces.
+     */
+    public boolean hasFreeformWorkspaceSupport() {
+        if (mPm == null) return false;
+
+        return mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+    }
+
     /** Returns whether the recents is currently running */
     public boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask,
             MutableBoolean isHomeTopMost) {
@@ -310,14 +322,14 @@
      * Returns whether the given stack id is the home stack id.
      */
     public static boolean isHomeStack(int stackId) {
-        return stackId == ActivityManager.HOME_STACK_ID;
+        return stackId == HOME_STACK_ID;
     }
 
     /**
      * Returns whether the given stack id is the freeform workspace stack id.
      */
     public static boolean isFreeformStack(int stackId) {
-        return stackId == ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+        return stackId == FREEFORM_WORKSPACE_STACK_ID;
     }
 
     /**
@@ -335,6 +347,19 @@
         return stackInfo != null;
     }
 
+    /**
+     * Cancels the current window transtion to/from Recents for the given task id.
+     */
+    public void cancelWindowTransition(int taskId) {
+        if (mWm == null) return;
+
+        try {
+            WindowManagerGlobal.getWindowManagerService().cancelTaskWindowTransition(taskId);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
     /** Returns the top task thumbnail for the given task id */
     public Bitmap getTaskThumbnail(int taskId) {
         if (mAm == null) return null;
@@ -714,7 +739,7 @@
 
         try {
             // Use the home stack bounds
-            ActivityManager.StackInfo stackInfo = mIam.getStackInfo(ActivityManager.HOME_STACK_ID);
+            ActivityManager.StackInfo stackInfo = mIam.getStackInfo(HOME_STACK_ID);
             if (stackInfo != null) {
                 windowRect.set(stackInfo.bounds);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 6c83b87..62493d6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -133,9 +133,11 @@
             Task task = new Task(taskKey, (t.id != INVALID_TASK_ID), t.affiliatedTaskId,
                     t.affiliatedTaskColor, activityLabel, contentDescription, activityIcon,
                     activityColor, (i == (taskCount - 1)), config.lockToAppEnabled, icon,
-                    iconFilename);
+                    iconFilename, t.bounds);
             task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false);
-            if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
+            if (DEBUG) {
+                Log.d(TAG, activityLabel + " bounds: " + t.bounds);
+            }
 
             if (task.isFreeformTask()) {
                 freeformTasks.add(task);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 71e3957..bba453a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -560,6 +560,11 @@
         ActivityInfo activityInfo = mActivityInfoCache.get(cn);
         if (activityInfo == null) {
             activityInfo = ssp.getActivityInfo(cn, taskKey.userId);
+            if (cn == null || activityInfo == null) {
+                Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " +
+                        activityInfo);
+                return null;
+            }
             mActivityInfoCache.put(cn, activityInfo);
         }
         return activityInfo;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 60051b8..51a1ebc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import com.android.systemui.recents.misc.Utilities;
 
@@ -105,6 +106,7 @@
     public boolean lockToTaskEnabled;
     public Bitmap icon;
     public String iconFilename;
+    public Rect bounds;
 
     private TaskCallbacks mCb;
 
@@ -115,7 +117,7 @@
     public Task(TaskKey key, boolean isActive, int taskAffiliation, int taskAffiliationColor,
                 String activityTitle, String contentDescription, Drawable activityIcon,
                 int colorPrimary, boolean lockToThisTask, boolean lockToTaskEnabled, Bitmap icon,
-                String iconFilename) {
+                String iconFilename, Rect bounds) {
         boolean isInAffiliationGroup = (taskAffiliation != key.id);
         boolean hasAffiliationGroupColor = isInAffiliationGroup && (taskAffiliationColor != 0);
         this.key = key;
@@ -132,6 +134,7 @@
         this.lockToTaskEnabled = lockToTaskEnabled;
         this.icon = icon;
         this.iconFilename = iconFilename;
+        this.bounds = bounds;
     }
 
     /** Copies the other task. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
index 6d11f14..67a6a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.recents.model;
 
+import android.util.Log;
 import android.util.LruCache;
 import android.util.SparseArray;
 
@@ -29,6 +30,8 @@
  */
 public class TaskKeyLruCache<V> {
 
+    private static final String TAG = "TaskKeyLruCache";
+
     private final SparseArray<Task.TaskKey> mKeys = new SparseArray<>();
     private final LruCache<Integer, V> mCache;
 
@@ -71,6 +74,10 @@
 
     /** Puts an entry in the cache for a specific key. */
     final void put(Task.TaskKey key, V value) {
+        if (key == null || value == null) {
+            Log.e(TAG, "Unexpected null key or value: " + key + ", " + value);
+            return;
+        }
         mKeys.put(key.id, key);
         mCache.put(key.id, value);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
index ce993c5..a97a2a8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -17,10 +17,11 @@
 package com.android.systemui.recents.views;
 
 import android.util.Log;
+import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 
-import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * The layout logic for the contents of the freeform workspace.
@@ -33,6 +34,7 @@
     // The number of cells in the freeform workspace
     private int mFreeformCellXCount;
     private int mFreeformCellYCount;
+
     // The width and height of the cells in the freeform workspace
     private int mFreeformCellWidth;
     private int mFreeformCellHeight;
@@ -44,22 +46,26 @@
      * Updates the layout for each of the freeform workspace tasks.  This is called after the stack
      * layout is updated.
      */
-    public void update(ArrayList<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
+    public void update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
+        mTaskIndexMap.clear();
+
         int numFreeformTasks = stackLayout.mNumFreeformTasks;
         if (!freeformTasks.isEmpty()) {
             // Calculate the cell width/height depending on the number of freeform tasks
             mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
-            mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks / mFreeformCellXCount));
-            mFreeformCellWidth = stackLayout.mFreeformRect.width() / mFreeformCellXCount;
+            mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks /
+                    mFreeformCellXCount));
             // For now, make the cells square
+            mFreeformCellWidth = Math.min(stackLayout.mFreeformRect.width() / mFreeformCellXCount,
+                    stackLayout.mFreeformRect.height() / mFreeformCellYCount);
             mFreeformCellHeight = mFreeformCellWidth;
 
             // Put each of the tasks in the progress map at a fixed index (does not need to actually
             // map to a scroll position, just by index)
             int taskCount = freeformTasks.size();
-            for (int i = 0; i < taskCount; i++) {
+            for (int i = taskCount - 1; i >= 0; i--) {
                 Task task = freeformTasks.get(i);
-                mTaskIndexMap.put(task.key, i);
+                mTaskIndexMap.put(task.key, taskCount - i - 1);
             }
 
             if (DEBUG) {
@@ -74,24 +80,23 @@
     /**
      * Returns whether the transform is available for the given task.
      */
-    public boolean isTransformAvailable(Task task, float stackScroll,
-            TaskStackLayoutAlgorithm stackLayout) {
-        if (stackLayout.mNumFreeformTasks == 0 || task == null ||
-                !mTaskIndexMap.containsKey(task.key)) {
+    public boolean isTransformAvailable(Task task, TaskStackLayoutAlgorithm stackLayout) {
+        if (stackLayout.mNumFreeformTasks == 0 || task == null) {
             return false;
         }
-        return stackScroll > stackLayout.mStackEndScrollP;
+        return mTaskIndexMap.containsKey(task.key);
     }
 
     /**
      * Returns the transform for the given task.  Any rect returned will be offset by the actual
      * transform for the freeform workspace.
      */
-    public TaskViewTransform getTransform(Task task, float stackScroll,
-            TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
-        if (Float.compare(stackScroll, stackLayout.mStackEndScrollP) > 0) {
+    public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
+            TaskStackLayoutAlgorithm stackLayout) {
+        if (mTaskIndexMap.containsKey(task.key)) {
             // This is a freeform task, so lay it out in the freeform workspace
             int taskIndex = mTaskIndexMap.get(task.key);
+            int topOffset = (stackLayout.mFreeformRect.top - stackLayout.mTaskRect.top);
             int x = taskIndex % mFreeformCellXCount;
             int y = taskIndex / mFreeformCellXCount;
             float scale = (float) mFreeformCellWidth / stackLayout.mTaskRect.width();
@@ -99,8 +104,13 @@
             int scaleYOffset = (int) (((1f - scale) * stackLayout.mTaskRect.height()) / 2);
             transformOut.scale = scale * 0.9f;
             transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
-            transformOut.translationY = y * mFreeformCellHeight - scaleYOffset;
+            transformOut.translationY = topOffset + y * mFreeformCellHeight - scaleYOffset;
+            transformOut.translationZ = stackLayout.mMaxTranslationZ;
+            transformOut.rect.set(stackLayout.mTaskRect);
+            transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
+            Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
             transformOut.visible = true;
+            transformOut.p = 0;
             return transformOut;
         }
         return null;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 422e917..d72e50e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -44,6 +44,7 @@
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
+import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsAppWidgetHostView;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
@@ -60,7 +61,9 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.app.ActivityManager.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 
 /**
  * This view is the the top level layout that contains TaskStacks (which are laid out according
@@ -488,14 +491,14 @@
             int destinationStack) {
         final int targetStackId = destinationStack != INVALID_STACK_ID ?
                 destinationStack : clickedTask.getTask().key.stackId;
-        if (targetStackId != ActivityManager.FREEFORM_WORKSPACE_STACK_ID
-                && targetStackId != ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID) {
+        if (targetStackId != FREEFORM_WORKSPACE_STACK_ID
+                && targetStackId != FULLSCREEN_WORKSPACE_STACK_ID) {
             return null;
         }
         // If this is a full screen stack, the transition will be towards the single, full screen
         // task. We only need the transition spec for this task.
         List<AppTransitionAnimationSpec> specs = new ArrayList<>();
-        if (targetStackId == ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID) {
+        if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
             specs.add(createThumbnailHeaderAnimationSpec(
                     stackView, offsetX, offsetY, stackScroll, clickedTask,
                     clickedTask.getTask().key.id, ADD_HEADER_BITMAP));
@@ -587,6 +590,20 @@
         return new AppTransitionAnimationSpec(taskId, b, rect);
     }
 
+    /**
+     * Cancels any running window transitions for the launched task (the task animating into
+     * Recents).
+     */
+    private void cancelLaunchedTaskWindowTransition(final Task task) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        RecentsConfiguration config = Recents.getConfiguration();
+        RecentsActivityLaunchState launchState = config.getLaunchState();
+        if (launchState.launchedToTaskId != -1 &&
+                launchState.launchedToTaskId != task.key.id) {
+            ssp.cancelWindowTransition(launchState.launchedToTaskId);
+        }
+    }
+
     /**** TaskStackView.TaskStackCallbacks Implementation ****/
 
     @Override
@@ -617,34 +634,37 @@
 
         // Compute the thumbnail to scale up from
         final SystemServicesProxy ssp = Recents.getSystemServices();
+        boolean screenPinningRequested = false;
         ActivityOptions opts = null;
         ActivityOptions.OnAnimationStartedListener animStartedListener = null;
         if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
                 task.thumbnail.getHeight() > 0) {
-            if (lockToTask) {
-                animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
-                    boolean mTriggered = false;
-                    @Override
-                    public void onAnimationStarted() {
-                        if (!mTriggered) {
-                            postDelayed(new Runnable() {
-                                @Override
-                                public void run() {
-                                    EventBus.getDefault().send(new ScreenPinningRequestEvent(
-                                            getContext(), ssp));
-                                }
-                            }, 350);
-                            mTriggered = true;
-                        }
+            animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
+                @Override
+                public void onAnimationStarted() {
+                    // If we are launching into another task, cancel the previous task's
+                    // window transition
+                    cancelLaunchedTaskWindowTransition(task);
+
+                    if (lockToTask) {
+                        // Request screen pinning after the animation runs
+                        postDelayed(new Runnable() {
+                            @Override
+                            public void run() {
+                                EventBus.getDefault().send(new ScreenPinningRequestEvent(
+                                        getContext(), ssp));
+                            }
+                        }, 350);
                     }
-                };
-            }
+                }
+            };
             postDrawHeaderThumbnailTransitionRunnable(stackView, tv, offsetX, offsetY, stackScroll,
                     animStartedListener, destinationStack);
             opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView,
                     Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8).createAshmemBitmap(),
                     offsetX, offsetY, (int) transform.rect.width(), (int) transform.rect.height(),
                     sourceView.getHandler(), animStartedListener);
+            screenPinningRequested = true;
         } else {
             opts = ActivityOptions.makeBasic();
         }
@@ -652,7 +672,7 @@
             opts.setBounds(bounds.isEmpty() ? null : bounds);
         }
         final ActivityOptions launchOpts = opts;
-        final boolean screenPinningRequested = (animStartedListener == null) && lockToTask;
+        final boolean finalScreenPinningRequested = screenPinningRequested;
         final Runnable launchRunnable = new Runnable() {
             @Override
             public void run() {
@@ -660,9 +680,11 @@
                     // Bring an active task to the foreground
                     ssp.moveTaskToFront(task.key.id, launchOpts);
                 } else {
-                    if (ssp.startActivityFromRecents(getContext(), task.key.id,
-                            task.activityLabel, launchOpts)) {
-                        if (screenPinningRequested) {
+                    if (ssp.startActivityFromRecents(getContext(), task.key.id, task.activityLabel,
+                            launchOpts)) {
+                        if (!finalScreenPinningRequested) {
+                            // If we have not requested this already to be run after the window
+                            // transition, then just run it now
                             EventBus.getDefault().send(new ScreenPinningRequestEvent(
                                     getContext(), ssp));
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 45d626e..a0713d7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -24,12 +24,14 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.misc.ParametricCurve;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.LinkedList;
 
 
 /**
@@ -44,14 +46,8 @@
     private static final float STACK_PEEK_MIN_SCALE = 0.85f;
     // The scale of the last task
     private static final float SINGLE_TASK_SCALE = 0.95f;
-    // The percentage of the height of the stack that we want to show the last task at
-    private static final float VISIBLE_LAST_TASK_HEIGHT_PCT = 0.45f;
     // The percentage of height of task to show between tasks
     private static final float VISIBLE_TASK_HEIGHT_BETWEEN_TASKS = 0.5f;
-    // The percentage between the maxStackScroll and the maxScroll where a given scroll will still
-    // snap back to the maxStackScroll instead of to the maxScroll (which shows the freeform
-    // workspace)
-    private static final float SNAP_TO_MAX_STACK_SCROLL_FACTOR = 0.3f;
 
     // A report of the visibility state of the stack
     public class VisibilityReport {
@@ -67,14 +63,36 @@
 
     Context mContext;
 
-    // This is the view bounds inset exactly by the search bar, but without the bottom inset
-    // see RecentsConfiguration.getTaskStackBounds()
-    public Rect mStackRect = new Rect();
-    // This is the task view bounds for layout (untransformed), the rect is top-aligned to the top
-    // of the stack rect
+    /*
+    +-------------------+
+    | SEARCH            |
+    +-------------------+
+    |+-----------------+|
+    || FREEFORM        ||
+    ||                 ||
+    ||                 ||
+    |+-----------------+|
+    |   +-----------+   |
+    | +---------------+ |
+    | |               | |
+    |+-----------------+|
+    || STACK           ||
+    +-------------------+
+     */
+
+    // The task bounds (untransformed) for layout.  This rect is anchored at mTaskRoot.
     public Rect mTaskRect = new Rect();
-    // The bounds of the freeform workspace, the rect is top-aligned to the top of the stack rect
+    // The freeform workspace bounds, inset from the top by the search bar, and is a fixed height
     public Rect mFreeformRect = new Rect();
+    // The freeform stack bounds, inset from the top by the search bar and freeform workspace, and
+    // runs to the bottom of the screen
+    private Rect mFreeformStackRect = new Rect();
+    // The stack bounds, inset from the top by the search bar, and runs to
+    // the bottom of the screen
+    private Rect mStackRect = new Rect();
+    // The current stack rect, can either by mFreeformStackRect or mStackRect depending on whether
+    // there is a freeform workspace
+    public Rect mCurrentStackRect;
     // This is the current system insets
     public Rect mSystemInsets = new Rect();
 
@@ -83,13 +101,6 @@
     // The largest scroll progress, at this value, the front most task will be visible above the
     // navigation bar
     float mMaxScrollP;
-    // The scroll progress at which bottom of the first task of the stack is aligned with the bottom
-    // of the stack
-    float mStackEndScrollP;
-    // The scroll progress that we actually want to scroll the user to when they want to go to the
-    // end of the stack (it accounts for the nav bar, so that the bottom of the task is offset from
-    // the bottom of the stack)
-    float mPreferredStackEndScrollP;
     // The initial progress that the scroller is set when you first enter recents
     float mInitialScrollP;
     // The task progress for the front-most task in the stack
@@ -109,13 +120,6 @@
     // The relative progress to ensure that the offset from the bottom of the stack to the bottom
     // of the task is respected
     float mStackBottomPOffset;
-    // The freeform workspace gap
-    int mFreeformWorkspaceGapOffset;
-    float mFreeformWorkspaceGapPOffset;
-    // The relative progress to ensure that the freeform workspace height + gap + stack bottom
-    // padding is respected
-    int mFreeformWorkspaceOffset;
-    float mFreeformWorkspacePOffset;
 
     // The last computed task counts
     int mNumStackTasks;
@@ -130,9 +134,6 @@
     // The freeform workspace layout
     FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
 
-    // Temporary task view transform
-    TaskViewTransform mTmpTransform = new TaskViewTransform();
-
     // Log function
     static ParametricCurve sCurve;
 
@@ -190,53 +191,57 @@
     }
 
     /**
-     * Computes the stack and task rects.
+     * Computes the stack and task rects.  The given task stack bounds is the whole bounds not
+     * including the search bar.
      */
     public void initialize(Rect taskStackBounds) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
         RecentsConfiguration config = Recents.getConfiguration();
         int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
         int heightPadding = mContext.getResources().getDimensionPixelSize(
                 R.dimen.recents_stack_top_padding);
 
-        // Compute the stack rect, inset from the given task stack bounds
-        mStackRect.set(taskStackBounds.left + widthPadding, taskStackBounds.top + heightPadding,
-                taskStackBounds.right - widthPadding, taskStackBounds.bottom);
+        // The freeform height is the visible height (not including system insets) - padding above
+        // freeform and below stack - gap between the freeform and stack
         mStackBottomOffset = mSystemInsets.bottom + heightPadding;
-
-        // Compute the task rect, align it to the top-center square in the stack rect
+        int ffHeight = (taskStackBounds.height() - 2 * heightPadding - mStackBottomOffset) / 2;
+        mFreeformRect.set(taskStackBounds.left + widthPadding,
+                taskStackBounds.top + heightPadding,
+                taskStackBounds.right - widthPadding,
+                taskStackBounds.top + heightPadding + ffHeight);
+        mFreeformStackRect.set(taskStackBounds.left + widthPadding,
+                taskStackBounds.top + heightPadding + ffHeight + heightPadding,
+                taskStackBounds.right - widthPadding,
+                taskStackBounds.bottom);
+        mStackRect.set(taskStackBounds.left + widthPadding,
+                taskStackBounds.top + heightPadding,
+                taskStackBounds.right - widthPadding,
+                taskStackBounds.bottom);
+        // Anchor the task rect to the top-center of the non-freeform stack rect
         int size = Math.min(mStackRect.width(), mStackRect.height() - mStackBottomOffset);
-        int xOffset = (mStackRect.width() - size) / 2;
-        mTaskRect.set(mStackRect.left + xOffset, mStackRect.top,
-                mStackRect.right - xOffset, mStackRect.top + size);
-
-        // Compute the freeform rect, align it to the top-left of the stack rect
-        mFreeformRect.set(mStackRect);
-        mFreeformRect.bottom = taskStackBounds.bottom - mStackBottomOffset;
+        mTaskRect.set(mStackRect.left, mStackRect.top,
+                mStackRect.left + size, mStackRect.top + size);
+        mCurrentStackRect = ssp.hasFreeformWorkspaceSupport() ? mFreeformStackRect : mStackRect;
 
         // Compute the progress offsets
         int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
                 R.dimen.recents_task_bar_height);
         int betweenAffiliationOffset = (int) (VISIBLE_TASK_HEIGHT_BETWEEN_TASKS * mTaskRect.height());
         mWithinAffiliationPOffset = sCurve.computePOffsetForScaledHeight(withinAffiliationOffset,
-                mStackRect);
+                mCurrentStackRect);
         mBetweenAffiliationPOffset = sCurve.computePOffsetForScaledHeight(betweenAffiliationOffset,
-                mStackRect);
+                mCurrentStackRect);
         mTaskHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height(),
-                mStackRect);
+                mCurrentStackRect);
         mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2,
-                mStackRect);
-        mStackBottomPOffset = sCurve.computePOffsetForHeight(mStackBottomOffset, mStackRect);
-        mFreeformWorkspaceGapOffset = mStackBottomOffset;
-        mFreeformWorkspaceGapPOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceGapOffset,
-                mStackRect);
-        mFreeformWorkspaceOffset = mFreeformWorkspaceGapOffset + mFreeformRect.height() +
-                mStackBottomOffset;
-        mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceOffset,
-                mStackRect);
+                mCurrentStackRect);
+        mStackBottomPOffset = sCurve.computePOffsetForHeight(mStackBottomOffset, mCurrentStackRect);
 
         if (DEBUG) {
             Log.d(TAG, "initialize");
             Log.d(TAG, "\tarclength: " + sCurve.getArcLength());
+            Log.d(TAG, "\tmFreeformRect: " + mFreeformRect);
+            Log.d(TAG, "\tmFreeformStackRect: " + mFreeformStackRect);
             Log.d(TAG, "\tmStackRect: " + mStackRect);
             Log.d(TAG, "\tmTaskRect: " + mTaskRect);
             Log.d(TAG, "\tmSystemInsets: " + mSystemInsets);
@@ -246,20 +251,9 @@
             Log.d(TAG, "\tmTaskHeightPOffset: " + mTaskHeightPOffset);
             Log.d(TAG, "\tmTaskHalfHeightPOffset: " + mTaskHalfHeightPOffset);
             Log.d(TAG, "\tmStackBottomPOffset: " + mStackBottomPOffset);
-            Log.d(TAG, "\tmFreeformWorkspacePOffset: " + mFreeformWorkspacePOffset);
-            Log.d(TAG, "\tmFreeformWorkspaceGapPOffset: " + mFreeformWorkspaceGapPOffset);
 
-            Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mStackRect));
-            Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mStackRect));
-
-            for (int height = 0; height <= 2000; height += 50) {
-                float p = sCurve.computePOffsetForScaledHeight(height, mStackRect);
-                float p2 = sCurve.computePOffsetForHeight(height, mStackRect);
-                Log.d(TAG, "offset: " + height + ", " +
-                        p + " => " + (mStackRect.bottom - sCurve.pToX(1f - p, mStackRect)) /
-                                sCurve.pToScale(1f - p) + ", " +
-                        p2 + " => " + (mStackRect.bottom - sCurve.pToX(1f - p2, mStackRect)));
-            }
+            Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mCurrentStackRect));
+            Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mCurrentStackRect));
         }
     }
 
@@ -279,7 +273,7 @@
         ArrayList<Task> tasks = stack.getTasks();
         if (tasks.isEmpty()) {
             mFrontMostTaskP = 0;
-            mMinScrollP = mMaxScrollP = mStackEndScrollP = mPreferredStackEndScrollP = 0;
+            mMinScrollP = mMaxScrollP = 0;
             mNumStackTasks = mNumFreeformTasks = 0;
             return;
         }
@@ -307,6 +301,10 @@
                 Task task = stackTasks.get(i);
                 mTaskProgressMap.put(task.key, pAtFrontMostTaskTop);
 
+                if (DEBUG) {
+                    Log.d(TAG, "Update: " + task.activityLabel + " p: " + pAtFrontMostTaskTop);
+                }
+
                 if (i < (taskCount - 1)) {
                     // Increment the peek height
                     float pPeek = task.group.isFrontMostTask(task) ?
@@ -316,37 +314,24 @@
             }
 
             mFrontMostTaskP = pAtFrontMostTaskTop;
-            // Set the stack end scroll progress to the point at which the bottom of the front-most
-            // task is aligned to the bottom of the stack
-            mStackEndScrollP = alignToStackBottom(pAtFrontMostTaskTop, mTaskHeightPOffset);
             if (mNumStackTasks > 1) {
-                // Set the preferred stack end scroll progress to the point where the bottom of the
-                // front-most task is offset by the navbar and padding from the bottom of the stack
-                mPreferredStackEndScrollP = mStackEndScrollP + mStackBottomPOffset;
-
+                // Set the stack end scroll progress to the point at which the bottom of the front-most
+                // task is aligned to the bottom of the stack
+                mMaxScrollP = alignToStackBottom(pAtFrontMostTaskTop,
+                        mStackBottomPOffset + mTaskHeightPOffset);
                 // Basically align the back-most task such that the last two tasks would be visible
-                mMinScrollP = alignToStackBottom(pAtBackMostTaskTop, 2 *
-                        mBetweenAffiliationPOffset);
+                mMinScrollP = alignToStackBottom(pAtBackMostTaskTop,
+                        mStackBottomPOffset + mTaskHeightPOffset);
             } else {
                 // When there is a single item, then just make all the stack progresses the same
-                mPreferredStackEndScrollP = mStackEndScrollP;
-                mMinScrollP = mStackEndScrollP;
+                mMinScrollP = mMaxScrollP = 0;
             }
-        } else {
-            // TODO: In the case where there is only freeform tasks, then the scrolls should be
-            // set to zero
         }
 
         if (!freeformTasks.isEmpty()) {
-            // The max scroll includes the freeform workspace offset. As the scroll progress exceeds
-            // mStackEndScrollP up to mMaxScrollP, the stack will translate upwards and the freeform
-            // workspace will be visible
             mFreeformLayoutAlgorithm.update(freeformTasks, this);
-            mMaxScrollP = mStackEndScrollP + mFreeformWorkspacePOffset;
-            mInitialScrollP = isInitialStateFreeform(stack) ?
-                    mMaxScrollP : mPreferredStackEndScrollP;
+            mInitialScrollP = mMaxScrollP;
         } else {
-            mMaxScrollP = mPreferredStackEndScrollP;
             mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset);
         }
 
@@ -354,7 +339,6 @@
             Log.d(TAG, "mNumStackTasks: " + mNumStackTasks);
             Log.d(TAG, "mNumFreeformTasks: " + mNumFreeformTasks);
             Log.d(TAG, "mMinScrollP: " + mMinScrollP);
-            Log.d(TAG, "mStackEndScrollP: " + mStackEndScrollP);
             Log.d(TAG, "mMaxScrollP: " + mMaxScrollP);
         }
     }
@@ -369,28 +353,28 @@
             return new VisibilityReport(1, 1);
         }
 
-        // If there are freeform tasks, then they will be the only ones visible
-        int freeformTaskCount = 0;
-        for (Task t : tasks) {
-            if (t.isFreeformTask()) {
-                freeformTaskCount++;
-            }
-        }
-        if (freeformTaskCount > 0) {
-            return new VisibilityReport(freeformTaskCount, freeformTaskCount);
+        // Quick return when there are no stack tasks
+        if (mNumStackTasks == 0) {
+            return new VisibilityReport(Math.max(mNumFreeformTasks, 1),
+                    Math.max(mNumFreeformTasks, 1));
         }
 
         // Otherwise, walk backwards in the stack and count the number of tasks and visible
-        // thumbnails
+        // thumbnails and add that to the total freeform task count
         int taskHeight = mTaskRect.height();
         int taskBarHeight = mContext.getResources().getDimensionPixelSize(
                 R.dimen.recents_task_bar_height);
-        int numVisibleTasks = 1;
-        int numVisibleThumbnails = 1;
-        float progress = mTaskProgressMap.get(tasks.get(tasks.size() - 1).key) - mInitialScrollP;
-        int prevScreenY = sCurve.pToX(progress, mStackRect);
+        int numVisibleTasks = Math.max(mNumFreeformTasks, 1);
+        int numVisibleThumbnails = Math.max(mNumFreeformTasks, 1);
+        Task firstNonFreeformTask = tasks.get(tasks.size() - mNumFreeformTasks - 1);
+        float progress = mTaskProgressMap.get(firstNonFreeformTask.key) - mInitialScrollP;
+        int prevScreenY = sCurve.pToX(progress, mCurrentStackRect);
         for (int i = tasks.size() - 2; i >= 0; i--) {
             Task task = tasks.get(i);
+            if (task.isFreeformTask()) {
+                continue;
+            }
+
             progress = mTaskProgressMap.get(task.key) - mInitialScrollP;
             if (progress < 0) {
                 break;
@@ -399,7 +383,7 @@
             if (isFrontMostTaskInGroup) {
                 float scaleAtP = sCurve.pToScale(progress);
                 int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
-                int screenY = sCurve.pToX(progress, mStackRect) + scaleYOffsetAtP;
+                int screenY = sCurve.pToX(progress, mCurrentStackRect) + scaleYOffsetAtP;
                 boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
                 if (hasVisibleThumbnail) {
                     numVisibleThumbnails++;
@@ -431,17 +415,8 @@
      */
     public TaskViewTransform getStackTransform(Task task, float stackScroll,
             TaskViewTransform transformOut, TaskViewTransform prevTransform) {
-        if (mFreeformLayoutAlgorithm.isTransformAvailable(task, stackScroll, this)) {
-            mFreeformLayoutAlgorithm.getTransform(task, stackScroll, transformOut, this);
-            if (transformOut.visible) {
-                getFreeformWorkspaceBounds(stackScroll, mTmpTransform);
-                transformOut.translationY += mTmpTransform.translationY;
-                transformOut.translationZ = mMaxTranslationZ;
-                transformOut.rect.set(mTaskRect);
-                transformOut.rect.offset(0, transformOut.translationY);
-                Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
-                transformOut.p = 0;
-            }
+        if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
+            mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
             return transformOut;
         } else {
             // Return early if we have an invalid index
@@ -463,32 +438,25 @@
             // modulate some values directly
             float pTaskRelative = mMinScrollP - stackScroll;
             float scale = (mNumFreeformTasks > 0) ? 1f : SINGLE_TASK_SCALE;
-            int topOffset = (mStackRect.height() - mTaskRect.height()) / 2;
+            int topOffset = (mCurrentStackRect.top - mTaskRect.top) +
+                    (mCurrentStackRect.height() - mTaskRect.height()) / 2;
             transformOut.scale = scale;
-            transformOut.translationX = 0;
-            transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height()));
+            transformOut.translationX = (mStackRect.width() - mTaskRect.width()) / 2;
+            transformOut.translationY = (int) (topOffset + (pTaskRelative * mCurrentStackRect.height()));
             transformOut.translationZ = mMaxTranslationZ;
             transformOut.rect.set(mTaskRect);
-            transformOut.rect.offset(0, transformOut.translationY);
+            transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
             Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
             transformOut.visible = true;
             transformOut.p = pTaskRelative;
             return transformOut;
 
         } else {
-            // Once we scroll past the preferred stack end scroll, then we should start translating
-            // the cards in screen space and lock their final state at the end stack progress
-            int overscrollYOffset = 0;
-            if (mNumFreeformTasks > 0 && stackScroll > mStackEndScrollP) {
-                float stackOverscroll = (stackScroll - mPreferredStackEndScrollP) /
-                        (mFreeformWorkspacePOffset - mFreeformWorkspaceGapPOffset);
-               overscrollYOffset = (int) (Math.max(0, stackOverscroll) *
-                       (mFreeformWorkspaceOffset - mFreeformWorkspaceGapPOffset));
-                stackScroll = Math.min(mPreferredStackEndScrollP, stackScroll);
-            }
-
             float pTaskRelative = taskProgress - stackScroll;
             float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
+            if (DEBUG) {
+                Log.d(TAG, "getStackTransform (normal): " + taskProgress + ", " + stackScroll);
+            }
 
             // If the task top is outside of the bounds below the screen, then immediately reset it
             if (pTaskRelative > 1f) {
@@ -508,18 +476,18 @@
             float scale = sCurve.pToScale(pBounded);
             int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
             transformOut.scale = scale;
-            transformOut.translationX = 0;
-            transformOut.translationY = sCurve.pToX(pBounded, mStackRect) - mStackRect.top -
-                    scaleYOffset - overscrollYOffset;
+            transformOut.translationX = (mStackRect.width() - mTaskRect.width()) / 2;
+            transformOut.translationY = (mCurrentStackRect.top - mTaskRect.top) +
+                    (sCurve.pToX(pBounded, mCurrentStackRect) - mCurrentStackRect.top) -
+                    scaleYOffset;
             transformOut.translationZ = Math.max(mMinTranslationZ,
                     mMinTranslationZ + (pBounded * (mMaxTranslationZ - mMinTranslationZ)));
             transformOut.rect.set(mTaskRect);
-            transformOut.rect.offset(0, transformOut.translationY);
+            transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
             Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
             transformOut.visible = true;
             transformOut.p = pTaskRelative;
             if (DEBUG) {
-                Log.d(TAG, "getStackTransform (normal): " + taskProgress + ", " + stackScroll);
                 Log.d(TAG, "\t" + transformOut);
             }
 
@@ -528,61 +496,6 @@
     }
 
     /**
-     * Returns whether this stack should be initialized to show the freeform workspace or not.
-     */
-    public boolean isInitialStateFreeform(TaskStack stack) {
-        Task launchTarget = stack.getLaunchTarget();
-        if (launchTarget != null) {
-            return launchTarget.isFreeformTask();
-        }
-        Task frontTask = stack.getFrontMostTask();
-        if (frontTask != null) {
-            return frontTask.isFreeformTask();
-        }
-        return false;
-    }
-
-    /**
-     * Update/get the transform
-     */
-    public TaskViewTransform getFreeformWorkspaceBounds(float stackScroll,
-            TaskViewTransform transformOut) {
-        transformOut.reset();
-        if (mNumFreeformTasks == 0) {
-            return transformOut;
-        }
-
-        if (stackScroll > mStackEndScrollP) {
-            // mStackEndScroll is the point at which the first stack task is bottom aligned with the
-            // stack, so we offset from on the stack rect height.
-            float stackOverscroll = (Math.max(0, stackScroll - mStackEndScrollP)) /
-                    mFreeformWorkspacePOffset;
-            int overscrollYOffset = (int) (stackOverscroll * mFreeformWorkspaceOffset);
-            transformOut.scale = 1f;
-            transformOut.alpha = 1f;
-            transformOut.translationY = mStackRect.height() + mFreeformWorkspaceGapOffset -
-                    overscrollYOffset;
-            transformOut.rect.set(mFreeformRect);
-            transformOut.rect.offset(0, transformOut.translationY);
-            Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
-            transformOut.visible = true;
-        }
-        return transformOut;
-    }
-
-    /**
-     * Returns the preferred maximum scroll position for a stack at the given {@param scroll}.
-     */
-    public float getPreferredMaxScrollPosition(float scroll) {
-        float maxStackScrollBounds = mStackEndScrollP + SNAP_TO_MAX_STACK_SCROLL_FACTOR *
-                (mMaxScrollP - mStackEndScrollP);
-        if (scroll < maxStackScrollBounds) {
-            return mPreferredStackEndScrollP;
-        }
-        return mMaxScrollP;
-    }
-
-    /**
      * Returns the untransformed task view bounds.
      */
     public Rect getUntransformedTaskViewBounds() {
@@ -604,7 +517,7 @@
      * screen along the arc-length proportionally (1/arclength).
      */
     public float getDeltaPForY(int downY, int y) {
-        float deltaP = (float) (y - downY) / mStackRect.height() * (1f / sCurve.getArcLength());
+        float deltaP = (float) (y - downY) / mCurrentStackRect.height() * (1f / sCurve.getArcLength());
         return -deltaP;
     }
 
@@ -613,7 +526,7 @@
      * of the curve, map back to the screen y.
      */
     public int getYForDeltaP(float downScrollP, float p) {
-        int y = (int) ((p - downScrollP) * mStackRect.height() * sCurve.getArcLength());
+        int y = (int) ((p - downScrollP) * mCurrentStackRect.height() * sCurve.getArcLength());
         return -y;
     }
 
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 b266eaa..14d75a8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -58,7 +58,7 @@
 import java.util.Iterator;
 import java.util.List;
 
-import static android.app.ActivityManager.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 
 
 /* The visual representation of a task stack view */
@@ -281,6 +281,7 @@
 
     /**
      * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
+     * This call ignores freeform tasks.
      */
     private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
                                        ArrayList<Task> tasks,
@@ -306,8 +307,16 @@
         // Update the stack transforms
         TaskViewTransform prevTransform = null;
         for (int i = taskCount - 1; i >= 0; i--) {
-            TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(tasks.get(i),
-                    stackScroll, taskTransforms.get(i), prevTransform);
+            Task task = tasks.get(i);
+            if (task.isFreeformTask()) {
+                continue;
+            }
+
+            TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll,
+                    taskTransforms.get(i), prevTransform);
+            if (DEBUG) {
+                Log.d(TAG, "updateStackTransform: " + i + ", " + transform.visible);
+            }
             if (transform.visible) {
                 if (frontMostVisibleIndex < 0) {
                     frontMostVisibleIndex = i;
@@ -327,7 +336,7 @@
 
             if (boundTranslationsToRect) {
                 transform.translationY = Math.min(transform.translationY,
-                        mLayoutAlgorithm.mStackRect.bottom);
+                        mLayoutAlgorithm.mCurrentStackRect.bottom);
             }
             prevTransform = transform;
         }
@@ -344,13 +353,13 @@
             // Get all the task transforms
             ArrayList<Task> tasks = mStack.getTasks();
             float stackScroll = mStackScroller.getStackScroll();
-            int[] visibleRange = mTmpVisibleRange;
-            boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
-                    stackScroll, visibleRange, false);
+            int[] visibleStackRange = mTmpVisibleRange;
+            boolean isValidVisibleStackRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
+                    stackScroll, visibleStackRange, false);
             boolean hasStackBackTransform = false;
             boolean hasStackFrontTransform = false;
             if (DEBUG) {
-                Log.d(TAG, "visibleRange: " + visibleRange[0] + " to " + visibleRange[1]);
+                Log.d(TAG, "visibleRange: " + visibleStackRange[0] + " to " + visibleStackRange[1]);
             }
 
             // Return all the invisible children to the pool
@@ -363,7 +372,8 @@
                 TaskView tv = taskViews.get(i);
                 Task task = tv.getTask();
                 int taskIndex = mStack.indexOfTask(task);
-                if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
+                if (task.isFreeformTask() ||
+                        visibleStackRange[1] <= taskIndex && taskIndex <= visibleStackRange[0]) {
                     mTmpTaskViewMap.put(task, tv);
                 } else {
                     if (tv.isFocusedTask()) {
@@ -371,16 +381,43 @@
                         lastFocusedTaskIndex = taskIndex;
                         resetFocusedTask();
                     }
+                    if (DEBUG) {
+                        Log.d(TAG, "returning to pool: " + task.key);
+                    }
                     mViewPool.returnViewToPool(tv);
                 }
             }
 
+            // Pick up all the freeform tasks
+            int firstVisStackIndex = isValidVisibleStackRange ? visibleStackRange[0] : 0;
+            for (int i = mStack.getTaskCount() - 1; i > firstVisStackIndex; i--) {
+                Task task = tasks.get(i);
+                if (!task.isFreeformTask()) {
+                    continue;
+                }
+                TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll,
+                        mCurrentTaskTransforms.get(i), null);
+                TaskView tv = mTmpTaskViewMap.get(task);
+                if (tv == null) {
+                    if (DEBUG) {
+                        Log.d(TAG, "picking up from pool: " + task.key);
+                    }
+                    tv = mViewPool.pickUpViewFromPool(task, task);
+                    if (mLayersDisabled) {
+                        tv.disableLayersForOneFrame();
+                    }
+                }
+
+                // Animate the task into place
+                tv.updateViewPropertiesToTaskTransform(transform,
+                        mStackViewsAnimationDuration, mRequestUpdateClippingListener);
+            }
+
             // Pick up all the newly visible children and update all the existing children
-            for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) {
+            for (int i = visibleStackRange[0]; isValidVisibleStackRange && i >= visibleStackRange[1]; i--) {
                 Task task = tasks.get(i);
                 TaskViewTransform transform = mCurrentTaskTransforms.get(i);
                 TaskView tv = mTmpTaskViewMap.get(task);
-                int taskIndex = mStack.indexOfTask(task);
 
                 if (tv == null) {
                     tv = mViewPool.pickUpViewFromPool(task, task);
@@ -409,29 +446,19 @@
                 }
 
                 // Animate the task into place
-                tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
+                tv.updateViewPropertiesToTaskTransform(transform,
                         mStackViewsAnimationDuration, mRequestUpdateClippingListener);
             }
 
             // Update the focus if the previous focused task was returned to the view pool
             if (lastFocusedTaskIndex != -1) {
-                if (lastFocusedTaskIndex < visibleRange[1]) {
-                    setFocusedTask(visibleRange[1], false, wasLastFocusedTaskAnimated);
+                if (lastFocusedTaskIndex < visibleStackRange[1]) {
+                    setFocusedTask(visibleStackRange[1], false, wasLastFocusedTaskAnimated);
                 } else {
-                    setFocusedTask(visibleRange[0], false, wasLastFocusedTaskAnimated);
+                    setFocusedTask(visibleStackRange[0], false, wasLastFocusedTaskAnimated);
                 }
             }
 
-            // Update the freeform workspace
-            mLayoutAlgorithm.getFreeformWorkspaceBounds(stackScroll, mTmpTransform);
-            if (mTmpTransform.visible) {
-                mTmpTransform.rect.roundOut(mTmpRect);
-                mFreeformWorkspaceBackground.setAlpha(255);
-                mFreeformWorkspaceBackground.setBounds(mTmpRect);
-            } else {
-                mFreeformWorkspaceBackground.setAlpha(0);
-            }
-
             // Reset the request-synchronize params
             mStackViewsAnimationDuration = 0;
             mStackViewsDirty = false;
@@ -491,6 +518,14 @@
         // Compute the min and max scroll values
         mLayoutAlgorithm.update(mStack);
 
+        // Update the freeform workspace
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        if (ssp.hasFreeformWorkspaceSupport()) {
+            mTmpRect.set(mLayoutAlgorithm.mFreeformRect);
+            mFreeformWorkspaceBackground.setAlpha(255);
+            mFreeformWorkspaceBackground.setBounds(mTmpRect);
+        }
+
         // Debug logging
         if (boundScrollToNewMinMax) {
             mStackScroller.boundScroll();
@@ -762,7 +797,7 @@
 
     /** Handler for the first layout. */
     void onFirstLayout() {
-        int offscreenY = mLayoutAlgorithm.mStackRect.bottom;
+        int offscreenY = mLayoutAlgorithm.mCurrentStackRect.bottom;
 
         // Find the launch target task
         Task launchTargetTask = mStack.getLaunchTarget();
@@ -863,7 +898,7 @@
         mStackScroller.stopScroller();
         mStackScroller.stopBoundScrollAnimation();
         // Animate all the task views out of view
-        ctx.offscreenTranslationY = mLayoutAlgorithm.mStackRect.bottom;
+        ctx.offscreenTranslationY = mLayoutAlgorithm.mCurrentStackRect.bottom;
 
         List<TaskView> taskViews = getTaskViews();
         int taskViewCount = taskViews.size();
@@ -929,6 +964,18 @@
         }
     }
 
+    /**
+     * Launches the freeform tasks.
+     */
+    public boolean launchFreeformTasks() {
+        Task frontTask = mStack.getFrontMostTask();
+        if (frontTask != null && frontTask.isFreeformTask()) {
+            onTaskViewClicked(getChildViewForTask(frontTask), frontTask, false);
+            return true;
+        }
+        return false;
+    }
+
     /**** TaskStackCallbacks Implementation ****/
 
     @Override
@@ -975,6 +1022,19 @@
 
             // Animate all the tasks into place
             requestSynchronizeStackViewsWithModel(200);
+        } else {
+            // Remove the view associated with this task, we can't rely on updateTransforms
+            // to work here because the task is no longer in the list
+            TaskView tv = getChildViewForTask(removedTask);
+            if (tv != null) {
+                mViewPool.returnViewToPool(tv);
+            }
+
+            // Update the min/max scroll and animate other task views into their new positions
+            updateMinMaxScroll(true);
+
+            // Animate all the tasks into place
+            requestSynchronizeStackViewsWithModel(200);
         }
 
         // Update the new front most task
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 6b92aed..3a2ed0f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -122,7 +122,7 @@
     /** Returns the bounded stack scroll */
     float getBoundedStackScroll(float scroll) {
         return Math.max(mLayoutAlgorithm.mMinScrollP,
-                Math.min(mLayoutAlgorithm.getPreferredMaxScrollPosition(scroll), scroll));
+                Math.min(mLayoutAlgorithm.mMaxScrollP, scroll));
     }
 
     /** Returns the amount that the absolute value of how much the scroll is out of bounds. */
@@ -194,7 +194,7 @@
     // TODO: Remove
     @Deprecated
     int progressToScrollRange(float p) {
-        return (int) (p * mLayoutAlgorithm.mStackRect.height());
+        return (int) (p * mLayoutAlgorithm.mCurrentStackRect.height());
     }
 
     /** Called from the view draw, computes the next scroll. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 9e6fb7b..59c9708 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -19,6 +19,7 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Rect;
 import android.util.Log;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -29,9 +30,11 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 
@@ -222,49 +225,12 @@
                 int activePointerIndex = ev.findPointerIndex(mActivePointerId);
                 int y = (int) ev.getY(activePointerIndex);
                 int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
-                float curScrollP = mScroller.getStackScroll();
                 if (mIsScrolling) {
-                    boolean hasFreeformTasks = mSv.mStack.hasFreeformTasks();
-                    if (hasFreeformTasks && velocity > 0 &&
-                            curScrollP > layoutAlgorithm.mStackEndScrollP) {
-                        // Snap to workspace
-                        float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
-                                layoutAlgorithm.mPreferredStackEndScrollP);
-                        mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY);
-                        mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                            @Override
-                            public void onAnimationUpdate(ValueAnimator animation) {
-                                float deltaP = layoutAlgorithm.getDeltaPForY(mDownY,
-                                        (Integer) animation.getAnimatedValue());
-                                float scroll = mDownScrollP + deltaP;
-                                mScroller.setStackScroll(scroll);
-                            }
-                        });
-                        mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity);
-                        mScrollFlingAnimator.start();
-                    } else if (hasFreeformTasks && velocity < 0 &&
-                            curScrollP > (layoutAlgorithm.mStackEndScrollP -
-                                    layoutAlgorithm.mTaskHalfHeightPOffset)) {
-                        // Snap to stack
-                        float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
-                                layoutAlgorithm.mMaxScrollP);
-                        mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY);
-                        mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                            @Override
-                            public void onAnimationUpdate(ValueAnimator animation) {
-                                float deltaP = layoutAlgorithm.getDeltaPForY(mDownY,
-                                        (Integer) animation.getAnimatedValue());
-                                float scroll = mDownScrollP + deltaP;
-                                mScroller.setStackScroll(scroll);
-                            }
-                        });
-                        mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity);
-                        mScrollFlingAnimator.start();
-                    } else if (mScroller.isScrollOutOfBounds()) {
+                    if (mScroller.isScrollOutOfBounds()) {
                         mScroller.animateBoundScroll();
                     } else if (Math.abs(velocity) > mMinimumVelocity) {
                         float minY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
-                                layoutAlgorithm.mPreferredStackEndScrollP);
+                                layoutAlgorithm.mMaxScrollP);
                         float maxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
                                 layoutAlgorithm.mMinScrollP);
                         mScroller.fling(mDownScrollP, mDownY, y, velocity, (int) minY, (int) maxY,
@@ -313,6 +279,17 @@
             return;
         }
 
+        // If tapping on the freeform workspace background, just launch the first freeform task
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        if (ssp.hasFreeformWorkspaceSupport()) {
+            Rect freeformRect = mSv.mLayoutAlgorithm.mFreeformRect;
+            if (freeformRect.top <= y && y <= freeformRect.bottom) {
+                if (mSv.launchFreeformTasks()) {
+                    return;
+                }
+            }
+        }
+
         // The user intentionally tapped on the background, which is like a tap on the "desktop".
         // Hide recents and transition to the launcher.
         EventBus.getDefault().send(new HideRecentsEvent(false, true));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 1e038c8..3f0000e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -124,7 +124,7 @@
     private static final boolean NOTIFICATION_CLICK_DEBUG = true;
 
     public static final boolean ENABLE_REMOTE_INPUT =
-            Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.enable_remote_input", false);
+            SystemProperties.getBoolean("debug.enable_remote_input", true);
     public static final boolean ENABLE_CHILD_NOTIFICATIONS = Build.IS_DEBUGGABLE
                     && SystemProperties.getBoolean("debug.child_notifs", false);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
index 4ac2c31..a51f62a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static android.app.ActivityManager.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 
 import android.animation.LayoutTransition;
 import android.annotation.Nullable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index ebe7785..fafedc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -128,7 +128,6 @@
     };
 
     protected void onExpandingFinished() {
-        endClosing();
         mBar.onExpandingFinished();
     }
 
@@ -143,6 +142,7 @@
     }
 
     protected final void notifyExpandingFinished() {
+        endClosing();
         if (mExpanding) {
             mExpanding = false;
             onExpandingFinished();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 749a080..535a8ef 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -91,7 +91,6 @@
 import com.android.internal.R;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -180,6 +179,8 @@
 
     private final MainHandler mMainHandler;
 
+    private MagnificationController mMagnificationController;
+
     private InteractionBridge mInteractionBridge;
 
     private AlertDialog mEnableTouchExplorationDialog;
@@ -1936,6 +1937,13 @@
         }
     }
 
+    MagnificationController getMagnificationController() {
+        if (mMagnificationController == null) {
+            mMagnificationController = new MagnificationController(mContext, this);
+        }
+        return mMagnificationController;
+    }
+
     /**
      * This class represents an accessibility service. It stores all per service
      * data required for the service management, provides API for starting/stopping the
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
new file mode 100644
index 0000000..b4411cf
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -0,0 +1,367 @@
+/*
+ * 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.server.accessibility;
+
+import com.android.internal.R;
+import com.android.server.LocalServices;
+
+import android.animation.ObjectAnimator;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.Property;
+import android.util.Slog;
+import android.view.MagnificationSpec;
+import android.view.WindowManagerInternal;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * This class is used to control and query the state of display magnification
+ * from the accessibility manager and related classes. It is responsible for
+ * holding the current state of magnification and animation, and it handles
+ * communication between the accessibility manager and window manager.
+ */
+class MagnificationController {
+    private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
+
+    private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
+    private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
+
+    private static final String PROPERTY_NAME_MAGNIFICATION_SPEC = "magnificationSpec";
+
+    private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
+    private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain();
+
+    private final Region mMagnifiedBounds = new Region();
+    private final Rect mTempRect = new Rect();
+
+    private final AccessibilityManagerService mAms;
+    private final WindowManagerInternal mWindowManager;
+    private final ValueAnimator mTransformationAnimator;
+
+    public MagnificationController(Context context, AccessibilityManagerService ams) {
+        mAms = ams;
+        mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+
+        final Property<MagnificationController, MagnificationSpec> property =
+                Property.of(MagnificationController.class, MagnificationSpec.class,
+                        PROPERTY_NAME_MAGNIFICATION_SPEC);
+        final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
+        final long animationDuration = context.getResources().getInteger(
+                R.integer.config_longAnimTime);
+        mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
+                mSentMagnificationSpec, mCurrentMagnificationSpec);
+        mTransformationAnimator.setDuration(animationDuration);
+        mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+    }
+
+    /**
+     * @return {@code true} if magnification is active, e.g. the scale
+     *         is > 1, {@code false} otherwise
+     */
+    public boolean isMagnifying() {
+        return mCurrentMagnificationSpec.scale > 1.0f;
+    }
+
+    /**
+     * Sets the magnified region.
+     *
+     * @param region the region to set
+     *  @param updateSpec {@code true} to update the scale and center based on
+     *                    the region bounds, {@code false} to leave them as-is
+     */
+    public void setMagnifiedRegion(Region region, boolean updateSpec) {
+        mMagnifiedBounds.set(region);
+
+        if (updateSpec) {
+            final Rect magnifiedFrame = mTempRect;
+            region.getBounds(magnifiedFrame);
+            final float scale = mSentMagnificationSpec.scale;
+            final float offsetX = mSentMagnificationSpec.offsetX;
+            final float offsetY = mSentMagnificationSpec.offsetY;
+            final float centerX = (-offsetX + magnifiedFrame.width() / 2) / scale;
+            final float centerY = (-offsetY + magnifiedFrame.height() / 2) / scale;
+            setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, false);
+        } else {
+            mAms.onMagnificationStateChanged();
+        }
+    }
+
+    /**
+     * Returns whether the magnified region contains the specified
+     * screen-relative coordinates.
+     *
+     * @param x the screen-relative X coordinate to check
+     * @param y the screen-relative Y coordinate to check
+     * @return {@code true} if the coordinate is contained within the
+     *         magnified region, or {@code false} otherwise
+     */
+    public boolean magnifiedRegionContains(float x, float y) {
+        return mMagnifiedBounds.contains((int) x, (int) y);
+    }
+
+    /**
+     * Populates the specified rect with the bounds of the magnified
+     * region.
+     *
+     * @param outBounds rect to populate with the bounds of the magnified
+     *                  region
+     */
+    public void getMagnifiedBounds(Rect outBounds) {
+        mMagnifiedBounds.getBounds(outBounds);
+    }
+
+    /**
+     * Returns the magnification scale. If an animation is in progress,
+     * this reflects the end state of the animation.
+     *
+     * @return the scale
+     */
+    public float getScale() {
+        return mCurrentMagnificationSpec.scale;
+    }
+
+    /**
+     * Returns the X offset of the magnification viewport. If an animation
+     * is in progress, this reflects the end state of the animation.
+     *
+     * @return the X offset
+     */
+    public float getOffsetX() {
+        return mCurrentMagnificationSpec.offsetX;
+    }
+
+    /**
+     * Returns the Y offset of the magnification viewport. If an animation
+     * is in progress, this reflects the end state of the animation.
+     *
+     * @return the Y offset
+     */
+    public float getOffsetY() {
+        return mCurrentMagnificationSpec.offsetY;
+    }
+
+    /**
+     * Returns the scale currently used by the window manager. If an
+     * animation is in progress, this reflects the current state of the
+     * animation.
+     *
+     * @return the scale currently used by the window manager
+     */
+    public float getSentScale() {
+        return mSentMagnificationSpec.scale;
+    }
+
+    /**
+     * Returns the X offset currently used by the window manager. If an
+     * animation is in progress, this reflects the current state of the
+     * animation.
+     *
+     * @return the X offset currently used by the window manager
+     */
+    public float getSentOffsetX() {
+        return mSentMagnificationSpec.offsetX;
+    }
+
+    /**
+     * Returns the Y offset currently used by the window manager. If an
+     * animation is in progress, this reflects the current state of the
+     * animation.
+     *
+     * @return the Y offset currently used by the window manager
+     */
+    public float getSentOffsetY() {
+        return mSentMagnificationSpec.offsetY;
+    }
+
+    /**
+     * Resets the magnification scale and center, optionally animating the
+     * transition.
+     *
+     * @param animate {@code true} to animate the transition, {@code false}
+     *                to transition immediately
+     */
+    public void reset(boolean animate) {
+        if (mTransformationAnimator.isRunning()) {
+            mTransformationAnimator.cancel();
+        }
+        mCurrentMagnificationSpec.clear();
+        if (animate) {
+            animateMagnificationSpec(mSentMagnificationSpec,
+                    mCurrentMagnificationSpec);
+        } else {
+            setMagnificationSpec(mCurrentMagnificationSpec);
+        }
+        final Rect bounds = mTempRect;
+        bounds.setEmpty();
+        mAms.onMagnificationStateChanged();
+    }
+
+    /**
+     * Scales the magnified region around the specified pivot point,
+     * optionally animating the transition. If animation is disabled, the
+     * transition is immediate.
+     *
+     * @param scale the target scale, must be >= 1
+     * @param animate {@code true} to animate the transition, {@code false}
+     *                to transition immediately
+     */
+    public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
+        final Rect magnifiedFrame = mTempRect;
+        mMagnifiedBounds.getBounds(magnifiedFrame);
+        final MagnificationSpec spec = mCurrentMagnificationSpec;
+        final float oldScale = spec.scale;
+        final float oldCenterX = (-spec.offsetX + magnifiedFrame.width() / 2) / oldScale;
+        final float oldCenterY = (-spec.offsetY + magnifiedFrame.height() / 2) / oldScale;
+        final float normPivotX = (-spec.offsetX + pivotX) / oldScale;
+        final float normPivotY = (-spec.offsetY + pivotY) / oldScale;
+        final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
+        final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
+        final float centerX = normPivotX + offsetX;
+        final float centerY = normPivotY + offsetY;
+        setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate);
+    }
+
+    /**
+     * Sets the center of the magnified region, optionally animating the
+     * transition. If animation is disabled, the transition is immediate.
+     *
+     * @param centerX the screen-relative X coordinate around which to
+     *                center
+     * @param centerY the screen-relative Y coordinate around which to
+     *                center
+     * @param animate {@code true} to animate the transition, {@code false}
+     *                to transition immediately
+     */
+    public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
+        setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.scale, centerX, centerY,
+                animate);
+    }
+
+    /**
+     * Sets the scale and center of the magnified region, optionally
+     * animating the transition. If animation is disabled, the transition
+     * is immediate.
+     *
+     * @param scale the target scale, must be >= 1
+     * @param centerX the screen-relative X coordinate around which to
+     *                center and scale
+     * @param centerY the screen-relative Y coordinate around which to
+     *                center and scale
+     * @param animate {@code true} to animate the transition, {@code false}
+     *                to transition immediately
+     */
+    public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
+            boolean animate) {
+        if (Float.compare(mCurrentMagnificationSpec.scale, scale) == 0
+                && Float.compare(mCurrentMagnificationSpec.offsetX, centerX) == 0
+                && Float.compare(mCurrentMagnificationSpec.offsetY, centerY) == 0) {
+            return;
+        }
+        if (mTransformationAnimator.isRunning()) {
+            mTransformationAnimator.cancel();
+        }
+        if (DEBUG_MAGNIFICATION_CONTROLLER) {
+            Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX + " offsetY: " + centerY);
+        }
+        updateMagnificationSpec(scale, centerX, centerY);
+        if (animate) {
+            animateMagnificationSpec(mSentMagnificationSpec,
+                    mCurrentMagnificationSpec);
+        } else {
+            setMagnificationSpec(mCurrentMagnificationSpec);
+        }
+        mAms.onMagnificationStateChanged();
+    }
+
+    /**
+     * Offsets the center of the magnified region.
+     *
+     * @param offsetX the amount in pixels to offset the X center
+     * @param offsetY the amount in pixels to offset the Y center
+     */
+    public void offsetMagnifiedRegionCenter(float offsetX, float offsetY) {
+        final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
+        mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
+                getMinOffsetX()), 0);
+        final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
+        mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
+                getMinOffsetY()), 0);
+        setMagnificationSpec(mCurrentMagnificationSpec);
+    }
+
+    private void updateMagnificationSpec(float scale, float magnifiedCenterX,
+            float magnifiedCenterY) {
+        final Rect magnifiedFrame = mTempRect;
+        mMagnifiedBounds.getBounds(magnifiedFrame);
+        mCurrentMagnificationSpec.scale = scale;
+        final int viewportWidth = magnifiedFrame.width();
+        final float nonNormOffsetX = viewportWidth / 2 - magnifiedCenterX * scale;
+        mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
+                getMinOffsetX()), 0);
+        final int viewportHeight = magnifiedFrame.height();
+        final float nonNormOffsetY = viewportHeight / 2 - magnifiedCenterY * scale;
+        mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
+                getMinOffsetY()), 0);
+    }
+
+    private float getMinOffsetX() {
+        final Rect magnifiedFrame = mTempRect;
+        mMagnifiedBounds.getBounds(magnifiedFrame);
+        final float viewportWidth = magnifiedFrame.width();
+        return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale;
+    }
+
+    private float getMinOffsetY() {
+        final Rect magnifiedFrame = mTempRect;
+        mMagnifiedBounds.getBounds(magnifiedFrame);
+        final float viewportHeight = magnifiedFrame.height();
+        return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale;
+    }
+
+    private void animateMagnificationSpec(MagnificationSpec fromSpec,
+            MagnificationSpec toSpec) {
+        mTransformationAnimator.setObjectValues(fromSpec, toSpec);
+        mTransformationAnimator.start();
+    }
+
+    private void setMagnificationSpec(MagnificationSpec spec) {
+        if (DEBUG_SET_MAGNIFICATION_SPEC) {
+            Slog.i(LOG_TAG, "Sending: " + spec);
+        }
+        mSentMagnificationSpec.scale = spec.scale;
+        mSentMagnificationSpec.offsetX = spec.offsetX;
+        mSentMagnificationSpec.offsetY = spec.offsetY;
+        mWindowManager.setMagnificationSpec(MagnificationSpec.obtain(spec));
+    }
+
+    private static class MagnificationSpecEvaluator implements TypeEvaluator<MagnificationSpec> {
+        private final MagnificationSpec mTempTransformationSpec = MagnificationSpec.obtain();
+
+        @Override
+        public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
+                MagnificationSpec toSpec) {
+            final MagnificationSpec result = mTempTransformationSpec;
+            result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction;
+            result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction;
+            result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction;
+            return result;
+        }
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
index 8845bc0..8feb167 100644
--- a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
@@ -16,9 +16,6 @@
 
 package com.android.server.accessibility;
 
-import android.animation.ObjectAnimator;
-import android.animation.TypeEvaluator;
-import android.animation.ValueAnimator;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -31,14 +28,12 @@
 import android.os.Message;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.util.Property;
 import android.util.Slog;
 import android.util.TypedValue;
 import android.view.GestureDetector;
 import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.InputDevice;
 import android.view.KeyEvent;
-import android.view.MagnificationSpec;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
@@ -48,7 +43,6 @@
 import android.view.ViewConfiguration;
 import android.view.WindowManagerInternal;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.DecelerateInterpolator;
 
 import com.android.internal.os.SomeArgs;
 import com.android.server.LocalServices;
@@ -101,10 +95,8 @@
 
     private static final boolean DEBUG_STATE_TRANSITIONS = false;
     private static final boolean DEBUG_DETECTING = false;
-    private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
     private static final boolean DEBUG_PANNING = false;
     private static final boolean DEBUG_SCALING = false;
-    private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
 
     private static final int STATE_DELEGATING = 1;
     private static final int STATE_DETECTING = 2;
@@ -134,8 +126,6 @@
     private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler;
     private final StateViewportDraggingHandler mStateViewportDraggingHandler;
 
-    private final AccessibilityManagerService mAms;
-
     private final int mUserId;
 
     private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout();
@@ -143,10 +133,6 @@
     private final int mTapDistanceSlop;
     private final int mMultiTapDistanceSlop;
 
-    private final long mLongAnimationDuration;
-
-    private final Region mMagnifiedBounds = new Region();
-
     private EventStreamTransformation mNext;
 
     private int mCurrentState;
@@ -194,13 +180,10 @@
         mContext = context;
         mUserId = userId;
         mWindowManager = LocalServices.getService(WindowManagerInternal.class);
-        mAms = service;
 
         mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout()
                 + mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
-        mLongAnimationDuration = context.getResources().getInteger(
-                com.android.internal.R.integer.config_longAnimTime);
         mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
 
@@ -209,7 +192,7 @@
         mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler(
                 context);
 
-        mMagnificationController = new MagnificationController(mLongAnimationDuration);
+        mMagnificationController = service.getMagnificationController();
         mScreenStateObserver = new ScreenStateObserver(context, mMagnificationController);
 
         mWindowManager.setMagnificationCallbacks(this);
@@ -230,19 +213,9 @@
         // If there was a rotation we have to update the center of the magnified
         // region since the old offset X/Y may be out of its acceptable range for
         // the new display width and height.
-        if (mUpdateMagnificationSpecOnNextBoundsChange) {
-            mUpdateMagnificationSpecOnNextBoundsChange = false;
-            MagnificationSpec spec = mMagnificationController.getMagnificationSpec();
-            Rect magnifiedFrame = mTempRect;
-            mMagnifiedBounds.getBounds(magnifiedFrame);
-            final float scale = spec.scale;
-            final float centerX = (-spec.offsetX + magnifiedFrame.width() / 2) / scale;
-            final float centerY = (-spec.offsetY + magnifiedFrame.height() / 2) / scale;
-            mMagnificationController.setScaleAndMagnifiedRegionCenter(scale, centerX,
-                    centerY, false);
-        }
-        mMagnifiedBounds.set(bounds);
-        mAms.onMagnificationStateChanged();
+        mMagnificationController.setMagnifiedRegion(
+                bounds, mUpdateMagnificationSpecOnNextBoundsChange);
+        mUpdateMagnificationSpecOnNextBoundsChange = false;
     }
 
     @Override
@@ -257,7 +230,7 @@
 
     private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
         Rect magnifiedFrame = mTempRect;
-        mMagnifiedBounds.getBounds(magnifiedFrame);
+        mMagnificationController.getMagnifiedBounds(magnifiedFrame);
         if (!magnifiedFrame.intersects(left, top, right, bottom)) {
             return;
         }
@@ -314,10 +287,12 @@
     }
 
     private void getMagnifiedFrameInContentCoords(Rect rect) {
-        MagnificationSpec spec = mMagnificationController.getMagnificationSpec();
-        mMagnifiedBounds.getBounds(rect);
-        rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
-        rect.scale(1.0f / spec.scale);
+        final float scale = mMagnificationController.getSentScale();
+        final float offsetX = mMagnificationController.getSentOffsetX();
+        final float offsetY = mMagnificationController.getSentOffsetY();
+        mMagnificationController.getMagnifiedBounds(rect);
+        rect.offset((int) -offsetX, (int) -offsetY);
+        rect.scale(1.0f / scale);
     }
 
     private void resetMagnificationIfNeeded() {
@@ -421,7 +396,7 @@
             final float eventX = event.getX();
             final float eventY = event.getY();
             if (mMagnificationController.isMagnifying()
-                    && mMagnifiedBounds.contains((int) eventX, (int) eventY)) {
+                    && mMagnificationController.magnifiedRegionContains(eventX, eventY)) {
                 final float scale = mMagnificationController.getScale();
                 final float scaledOffsetX = mMagnificationController.getOffsetX();
                 final float scaledOffsetY = mMagnificationController.getOffsetY();
@@ -623,7 +598,7 @@
                     }
                     final float eventX = event.getX();
                     final float eventY = event.getY();
-                    if (mMagnifiedBounds.contains((int) eventX, (int) eventY)) {
+                    if (mMagnificationController.magnifiedRegionContains(eventX, eventY)) {
                         if (mLastMoveOutsideMagnifiedRegion) {
                             mLastMoveOutsideMagnifiedRegion = false;
                             mMagnificationController.setMagnifiedRegionCenter(eventX,
@@ -696,8 +671,8 @@
             switch (action) {
                 case MotionEvent.ACTION_DOWN: {
                     mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
-                    if (!mMagnifiedBounds.contains((int) event.getX(),
-                            (int) event.getY())) {
+                    if (!mMagnificationController.magnifiedRegionContains(
+                            event.getX(), event.getY())) {
                         transitionToDelegatingStateAndClear();
                         return;
                     }
@@ -738,7 +713,8 @@
                         return;
                     }
                     mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
-                    if (!mMagnifiedBounds.contains((int) event.getX(), (int) event.getY())) {
+                    if (!mMagnificationController.magnifiedRegionContains(
+                            event.getX(), event.getY())) {
                          transitionToDelegatingStateAndClear();
                          return;
                     }
@@ -947,184 +923,6 @@
         }
     }
 
-    private final class MagnificationController {
-
-        private static final String PROPERTY_NAME_MAGNIFICATION_SPEC =
-                "magnificationSpec";
-
-        private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
-
-        private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain();
-
-        private final Rect mTempRect = new Rect();
-
-        private final ValueAnimator mTransformationAnimator;
-
-        public MagnificationController(long animationDuration) {
-            Property<MagnificationController, MagnificationSpec> property =
-                    Property.of(MagnificationController.class, MagnificationSpec.class,
-                    PROPERTY_NAME_MAGNIFICATION_SPEC);
-            TypeEvaluator<MagnificationSpec> evaluator = new TypeEvaluator<MagnificationSpec>() {
-                private final MagnificationSpec mTempTransformationSpec =
-                        MagnificationSpec.obtain();
-                @Override
-                public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
-                        MagnificationSpec toSpec) {
-                    MagnificationSpec result = mTempTransformationSpec;
-                    result.scale = fromSpec.scale
-                            + (toSpec.scale - fromSpec.scale) * fraction;
-                    result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX)
-                            * fraction;
-                    result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY)
-                            * fraction;
-                    return result;
-                }
-            };
-            mTransformationAnimator = ObjectAnimator.ofObject(this, property,
-                    evaluator, mSentMagnificationSpec, mCurrentMagnificationSpec);
-            mTransformationAnimator.setDuration((long) (animationDuration));
-            mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
-        }
-
-        public boolean isMagnifying() {
-            return mCurrentMagnificationSpec.scale > 1.0f;
-        }
-
-        public void reset(boolean animate) {
-            if (mTransformationAnimator.isRunning()) {
-                mTransformationAnimator.cancel();
-            }
-            mCurrentMagnificationSpec.clear();
-            if (animate) {
-                animateMangificationSpec(mSentMagnificationSpec,
-                        mCurrentMagnificationSpec);
-            } else {
-                setMagnificationSpec(mCurrentMagnificationSpec);
-            }
-            Rect bounds = mTempRect;
-            bounds.setEmpty();
-            mAms.onMagnificationStateChanged();
-        }
-
-        public float getScale() {
-            return mCurrentMagnificationSpec.scale;
-        }
-
-        public float getOffsetX() {
-            return mCurrentMagnificationSpec.offsetX;
-        }
-
-        public float getOffsetY() {
-            return mCurrentMagnificationSpec.offsetY;
-        }
-
-        public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
-            Rect magnifiedFrame = mTempRect;
-            mMagnifiedBounds.getBounds(magnifiedFrame);
-            MagnificationSpec spec = mCurrentMagnificationSpec;
-            final float oldScale = spec.scale;
-            final float oldCenterX = (-spec.offsetX + magnifiedFrame.width() / 2) / oldScale;
-            final float oldCenterY = (-spec.offsetY + magnifiedFrame.height() / 2) / oldScale;
-            final float normPivotX = (-spec.offsetX + pivotX) / oldScale;
-            final float normPivotY = (-spec.offsetY + pivotY) / oldScale;
-            final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
-            final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
-            final float centerX = normPivotX + offsetX;
-            final float centerY = normPivotY + offsetY;
-            setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate);
-        }
-
-        public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
-            setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.scale, centerX, centerY,
-                    animate);
-        }
-
-        public void offsetMagnifiedRegionCenter(float offsetX, float offsetY) {
-            final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
-            mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
-                    getMinOffsetX()), 0);
-            final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
-            mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
-                    getMinOffsetY()), 0);
-            setMagnificationSpec(mCurrentMagnificationSpec);
-        }
-
-        public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
-                boolean animate) {
-            if (Float.compare(mCurrentMagnificationSpec.scale, scale) == 0
-                    && Float.compare(mCurrentMagnificationSpec.offsetX,
-                            centerX) == 0
-                    && Float.compare(mCurrentMagnificationSpec.offsetY,
-                            centerY) == 0) {
-                return;
-            }
-            if (mTransformationAnimator.isRunning()) {
-                mTransformationAnimator.cancel();
-            }
-            if (DEBUG_MAGNIFICATION_CONTROLLER) {
-                Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX
-                        + " offsetY: " + centerY);
-            }
-            updateMagnificationSpec(scale, centerX, centerY);
-            if (animate) {
-                animateMangificationSpec(mSentMagnificationSpec,
-                        mCurrentMagnificationSpec);
-            } else {
-                setMagnificationSpec(mCurrentMagnificationSpec);
-            }
-            mAms.onMagnificationStateChanged();
-        }
-
-        public void updateMagnificationSpec(float scale, float magnifiedCenterX,
-                float magnifiedCenterY) {
-            Rect magnifiedFrame = mTempRect;
-            mMagnifiedBounds.getBounds(magnifiedFrame);
-            mCurrentMagnificationSpec.scale = scale;
-            final int viewportWidth = magnifiedFrame.width();
-            final float nonNormOffsetX = viewportWidth / 2 - magnifiedCenterX * scale;
-            mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
-                    getMinOffsetX()), 0);
-            final int viewportHeight = magnifiedFrame.height();
-            final float nonNormOffsetY = viewportHeight / 2 - magnifiedCenterY * scale;
-            mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
-                    getMinOffsetY()), 0);
-        }
-
-        private float getMinOffsetX() {
-            Rect magnifiedFrame = mTempRect;
-            mMagnifiedBounds.getBounds(magnifiedFrame);
-            final float viewportWidth = magnifiedFrame.width();
-            return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale;
-        }
-
-        private float getMinOffsetY() {
-            Rect magnifiedFrame = mTempRect;
-            mMagnifiedBounds.getBounds(magnifiedFrame);
-            final float viewportHeight = magnifiedFrame.height();
-            return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale;
-        }
-
-        private void animateMangificationSpec(MagnificationSpec fromSpec,
-                MagnificationSpec toSpec) {
-            mTransformationAnimator.setObjectValues(fromSpec, toSpec);
-            mTransformationAnimator.start();
-        }
-
-        public MagnificationSpec getMagnificationSpec() {
-            return mSentMagnificationSpec;
-        }
-
-        public void setMagnificationSpec(MagnificationSpec spec) {
-            if (DEBUG_SET_MAGNIFICATION_SPEC) {
-                Slog.i(LOG_TAG, "Sending: " + spec);
-            }
-            mSentMagnificationSpec.scale = spec.scale;
-            mSentMagnificationSpec.offsetX = spec.offsetX;
-            mSentMagnificationSpec.offsetY = spec.offsetY;
-            mWindowManager.setMagnificationSpec(MagnificationSpec.obtain(spec));
-        }
-    }
-
     private final class ScreenStateObserver extends BroadcastReceiver {
         private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
 
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 61fe62f..48e96aa 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -123,6 +123,7 @@
     private int mLastBatteryTemperature;
     private boolean mLastBatteryLevelCritical;
     private int mLastMaxChargingCurrent;
+    private int mLastMaxChargingVoltage;
 
     private int mInvalidCharger;
     private int mLastInvalidCharger;
@@ -339,6 +340,7 @@
                     + ", chargerUsbOnline=" + mBatteryProps.chargerUsbOnline
                     + ", chargerWirelessOnline=" + mBatteryProps.chargerWirelessOnline
                     + ", maxChargingCurrent" + mBatteryProps.maxChargingCurrent
+                    + ", maxChargingVoltage" + mBatteryProps.maxChargingVoltage
                     + ", batteryStatus=" + mBatteryProps.batteryStatus
                     + ", batteryHealth=" + mBatteryProps.batteryHealth
                     + ", batteryPresent=" + mBatteryProps.batteryPresent
@@ -370,6 +372,7 @@
                 mBatteryProps.batteryVoltage != mLastBatteryVoltage ||
                 mBatteryProps.batteryTemperature != mLastBatteryTemperature ||
                 mBatteryProps.maxChargingCurrent != mLastMaxChargingCurrent ||
+                mBatteryProps.maxChargingVoltage != mLastMaxChargingVoltage ||
                 mInvalidCharger != mLastInvalidCharger)) {
 
             if (mPlugType != mLastPlugType) {
@@ -497,6 +500,7 @@
             mLastBatteryVoltage = mBatteryProps.batteryVoltage;
             mLastBatteryTemperature = mBatteryProps.batteryTemperature;
             mLastMaxChargingCurrent = mBatteryProps.maxChargingCurrent;
+            mLastMaxChargingVoltage = mBatteryProps.maxChargingVoltage;
             mLastBatteryLevelCritical = mBatteryLevelCritical;
             mLastInvalidCharger = mInvalidCharger;
         }
@@ -522,7 +526,7 @@
         intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryProps.batteryTechnology);
         intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
         intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mBatteryProps.maxChargingCurrent);
-
+        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mBatteryProps.maxChargingVoltage);
         if (DEBUG) {
             Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED.  level:" + mBatteryProps.batteryLevel +
                     ", scale:" + BATTERY_SCALE + ", status:" + mBatteryProps.batteryStatus +
@@ -535,7 +539,8 @@
                     ", USB powered:" + mBatteryProps.chargerUsbOnline +
                     ", Wireless powered:" + mBatteryProps.chargerWirelessOnline +
                     ", icon:" + icon  + ", invalid charger:" + mInvalidCharger +
-                    ", maxChargingCurrent:" + mBatteryProps.maxChargingCurrent);
+                    ", maxChargingCurrent:" + mBatteryProps.maxChargingCurrent +
+                    ", maxChargingVoltage:" + mBatteryProps.maxChargingVoltage);
         }
 
         mHandler.post(new Runnable() {
@@ -766,6 +771,7 @@
                 pw.println("  USB powered: " + mBatteryProps.chargerUsbOnline);
                 pw.println("  Wireless powered: " + mBatteryProps.chargerWirelessOnline);
                 pw.println("  Max charging current: " + mBatteryProps.maxChargingCurrent);
+                pw.println("  Max charging voltage: " + mBatteryProps.maxChargingVoltage);
                 pw.println("  status: " + mBatteryProps.batteryStatus);
                 pw.println("  health: " + mBatteryProps.batteryHealth);
                 pw.println("  present: " + mBatteryProps.batteryPresent);
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 85187c7..a7879c6 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -562,6 +562,8 @@
     private static final int H_VOLUME_BROADCAST = 6;
     private static final int H_INTERNAL_BROADCAST = 7;
     private static final int H_VOLUME_UNMOUNT = 8;
+    private static final int H_PARTITION_FORGET = 9;
+    private static final int H_RESET = 10;
 
     class MountServiceHandler extends Handler {
         public MountServiceHandler(Looper looper) {
@@ -669,6 +671,16 @@
                     final Intent intent = (Intent) msg.obj;
                     mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
                             android.Manifest.permission.WRITE_MEDIA_STORAGE);
+                    break;
+                }
+                case H_PARTITION_FORGET: {
+                    final String partGuid = (String) msg.obj;
+                    forgetPartition(partGuid);
+                    break;
+                }
+                case H_RESET: {
+                    resetIfReadyAndConnected();
+                    break;
                 }
             }
         }
@@ -753,9 +765,7 @@
     }
 
     private void handleSystemReady() {
-        synchronized (mLock) {
-            resetIfReadyAndConnectedLocked();
-        }
+        resetIfReadyAndConnected();
 
         // Start scheduling nominally-daily fstrim operations
         MountServiceIdler.scheduleIdlePass(mContext);
@@ -793,7 +803,7 @@
         }
     }
 
-    private void addInternalVolume() {
+    private void addInternalVolumeLocked() {
         // Create a stub volume that represents internal storage
         final VolumeInfo internal = new VolumeInfo(VolumeInfo.ID_PRIVATE_INTERNAL,
                 VolumeInfo.TYPE_PRIVATE, null, null);
@@ -802,18 +812,22 @@
         mVolumes.put(internal.id, internal);
     }
 
-    private void resetIfReadyAndConnectedLocked() {
+    private void resetIfReadyAndConnected() {
         Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady
                 + ", mDaemonConnected=" + mDaemonConnected);
         if (mSystemReady && mDaemonConnected) {
-            final UserManager um = UserManager.get(mContext);
-            final List<UserInfo> users = um.getUsers();
+            final List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
             killMediaProvider(users);
 
-            mDisks.clear();
-            mVolumes.clear();
+            final int[] startedUsers;
+            synchronized (mLock) {
+                startedUsers = mStartedUsers;
 
-            addInternalVolume();
+                mDisks.clear();
+                mVolumes.clear();
+
+                addInternalVolumeLocked();
+            }
 
             try {
                 mConnector.execute("volume", "reset");
@@ -822,7 +836,7 @@
                 for (UserInfo user : users) {
                     mConnector.execute("volume", "user_added", user.id, user.serialNumber);
                 }
-                for (int userId : mStartedUsers) {
+                for (int userId : startedUsers) {
                     mConnector.execute("volume", "user_started", userId);
                 }
             } catch (NativeDaemonConnectorException e) {
@@ -898,9 +912,7 @@
     }
 
     private void handleDaemonConnected() {
-        synchronized (mLock) {
-            resetIfReadyAndConnectedLocked();
-        }
+        resetIfReadyAndConnected();
 
         /*
          * Now that we've done our initialization, release
@@ -1428,6 +1440,7 @@
         mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
                 null);
         mConnector.setDebug(true);
+        mConnector.setWarnIfHeld(mLock);
 
         Thread thread = new Thread(mConnector, VOLD_TAG);
         thread.start();
@@ -1445,7 +1458,9 @@
         userFilter.addAction(Intent.ACTION_USER_REMOVED);
         mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
 
-        addInternalVolume();
+        synchronized (mLock) {
+            addInternalVolumeLocked();
+        }
 
         // Add ourself to the Watchdog monitors if enabled.
         if (WATCHDOG_ENABLE) {
@@ -1791,10 +1806,11 @@
         waitForReady();
 
         Preconditions.checkNotNull(fsUuid);
+
         synchronized (mLock) {
             final VolumeRecord rec = mRecords.remove(fsUuid);
             if (rec != null && !TextUtils.isEmpty(rec.partGuid)) {
-                forgetPartition(rec.partGuid);
+                mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget();
             }
             mCallbacks.notifyVolumeForgotten(fsUuid);
 
@@ -1802,7 +1818,7 @@
             // reset vold so we bind into new volume into place.
             if (Objects.equals(mPrimaryStorageUuid, fsUuid)) {
                 mPrimaryStorageUuid = getDefaultPrimaryStorageUuid();
-                resetIfReadyAndConnectedLocked();
+                mHandler.obtainMessage(H_RESET).sendToTarget();
             }
 
             writeSettingsLocked();
@@ -1819,7 +1835,7 @@
                 final String fsUuid = mRecords.keyAt(i);
                 final VolumeRecord rec = mRecords.valueAt(i);
                 if (!TextUtils.isEmpty(rec.partGuid)) {
-                    forgetPartition(rec.partGuid);
+                    mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget();
                 }
                 mCallbacks.notifyVolumeForgotten(fsUuid);
             }
@@ -1830,7 +1846,7 @@
             }
 
             writeSettingsLocked();
-            resetIfReadyAndConnectedLocked();
+            mHandler.obtainMessage(H_RESET).sendToTarget();
         }
     }
 
@@ -1878,7 +1894,7 @@
             }
 
             writeSettingsLocked();
-            resetIfReadyAndConnectedLocked();
+            mHandler.obtainMessage(H_RESET).sendToTarget();
         }
     }
 
@@ -1915,7 +1931,7 @@
                 Slog.d(TAG, "Skipping move to/from primary physical");
                 onMoveStatusLocked(MOVE_STATUS_COPY_FINISHED);
                 onMoveStatusLocked(PackageManager.MOVE_SUCCEEDED);
-                resetIfReadyAndConnectedLocked();
+                mHandler.obtainMessage(H_RESET).sendToTarget();
 
             } else {
                 final VolumeInfo from = findStorageForUuid(mPrimaryStorageUuid);
diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/services/core/java/com/android/server/NativeDaemonConnector.java
index 519a2a3..e6b6074 100644
--- a/services/core/java/com/android/server/NativeDaemonConnector.java
+++ b/services/core/java/com/android/server/NativeDaemonConnector.java
@@ -28,6 +28,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 import com.google.android.collect.Lists;
 
 import java.io.FileDescriptor;
@@ -57,6 +58,7 @@
     private LocalLog mLocalLog;
 
     private volatile boolean mDebug = false;
+    private volatile Object mWarnIfHeld;
 
     private final ResponseQueue mResponseQueue;
 
@@ -107,6 +109,15 @@
         mDebug = debug;
     }
 
+    /**
+     * Yell loudly if someone tries making future {@link #execute(Command)}
+     * calls while holding a lock on the given object.
+     */
+    public void setWarnIfHeld(Object warnIfHeld) {
+        Preconditions.checkState(mWarnIfHeld == null);
+        mWarnIfHeld = Preconditions.checkNotNull(warnIfHeld);
+    }
+
     @Override
     public void run() {
         mCallbackHandler = new Handler(mLooper, this);
@@ -394,6 +405,11 @@
      */
     public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
             throws NativeDaemonConnectorException {
+        if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
+            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
+                    + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
+        }
+
         final long startTime = SystemClock.elapsedRealtime();
 
         final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java
index a0d305c..3f0664d 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java
@@ -30,6 +30,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
+import android.os.PowerManager;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.NtpTrustedTime;
@@ -75,6 +76,7 @@
     private SettingsObserver mSettingsObserver;
     // The last time that we successfully fetched the NTP time.
     private long mLastNtpFetchTime = NOT_SET;
+    private final PowerManager.WakeLock mWakeLock;
 
     // Normal polling frequency
     private final long mPollingIntervalMs;
@@ -104,6 +106,9 @@
                 com.android.internal.R.integer.config_ntpRetry);
         mTimeErrorThresholdMs = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_ntpThreshold);
+
+        mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE)).newWakeLock(
+                PowerManager.PARTIAL_WAKE_LOCK, TAG);
     }
 
     /** Initialize the receivers and initiate the first NTP request */
@@ -148,7 +153,15 @@
     private void onPollNetworkTime(int event) {
         // If Automatic time is not set, don't bother.
         if (!isAutomaticTimeRequested()) return;
+        mWakeLock.acquire();
+        try {
+            onPollNetworkTimeUnderWakeLock(event);
+        } finally {
+            mWakeLock.release();
+        }
+    }
 
+    private void onPollNetworkTimeUnderWakeLock(int event) {
         final long refTime = SystemClock.elapsedRealtime();
         // If NITZ time was received less than mPollingIntervalMs time ago,
         // no need to sync to NTP.
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index df6b1d6..befaaef 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -337,7 +337,7 @@
 
         ServiceRecord r = res.record;
 
-        if (!mAm.getUserManagerLocked().exists(r.userId)) {
+        if (!mAm.mUserController.exists(r.userId)) {
             Slog.d(TAG, "Trying to start service with non-existent user! " + r.userId);
             return null;
         }
@@ -1030,8 +1030,8 @@
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service
                 + " type=" + resolvedType + " callingUid=" + callingUid);
 
-        userId = mAm.handleIncomingUser(callingPid, callingUid, userId,
-                false, ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE, "service", null);
+        userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
+                ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE, "service", null);
 
         ServiceMap smap = getServiceMap(userId);
         final ComponentName comp = service.getComponent();
@@ -2333,7 +2333,8 @@
                 EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
                         sr.userId, sr.crashCount, sr.shortName, app.pid);
                 bringDownServiceLocked(sr);
-            } else if (!allowRestart || !mAm.isUserRunningLocked(sr.userId, false)) {
+            } else if (!allowRestart
+                    || !mAm.mUserController.isUserRunningLocked(sr.userId, false)) {
                 bringDownServiceLocked(sr);
             } else {
                 boolean canceled = scheduleServiceRestartLocked(sr, true);
@@ -2446,7 +2447,7 @@
             if (ActivityManager.checkUidPermission(
                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
                     uid) == PackageManager.PERMISSION_GRANTED) {
-                int[] users = mAm.getUsersLocked();
+                int[] users = mAm.mUserController.getUsers();
                 for (int ui=0; ui<users.length && res.size() < maxNum; ui++) {
                     ArrayMap<ComponentName, ServiceRecord> alls = getServices(users[ui]);
                     for (int i=0; i<alls.size() && res.size() < maxNum; i++) {
@@ -2580,7 +2581,7 @@
                 pw.print(mLastAnrDump);
                 pw.println();
             }
-            int[] users = mAm.getUsersLocked();
+            int[] users = mAm.mUserController.getUsers();
             for (int user : users) {
                 ServiceMap smap = getServiceMap(user);
                 boolean printed = false;
@@ -2817,7 +2818,7 @@
         ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
 
         synchronized (mAm) {
-            int[] users = mAm.getUsersLocked();
+            int[] users = mAm.mUserController.getUsers();
             if ("all".equals(name)) {
                 for (int user : users) {
                     ServiceMap smap = mServiceMap.get(user);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5aab804..546db3e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19,12 +19,11 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static com.android.internal.util.XmlUtils.readBooleanAttribute;
@@ -47,6 +46,7 @@
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
 import android.Manifest;
+import android.app.ActivityManager.StackId;
 import android.app.AppOpsManager;
 import android.app.ApplicationThreadNative;
 import android.app.BroadcastOptions;
@@ -207,7 +207,6 @@
 import android.os.IBinder;
 import android.os.IPermissionController;
 import android.os.IProcessInfoService;
-import android.os.IUserManager;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
@@ -375,6 +374,13 @@
     // we will consider it to be doing interaction for usage stats.
     static final int SERVICE_USAGE_INTERACTION_TIME = 30*60*1000;
 
+    // Maximum amount of time we will allow to elapse before re-reporting usage stats
+    // interaction with foreground processes.
+    static final long USAGE_STATS_INTERACTION_INTERVAL = 24*60*60*1000L;
+
+    // Maximum number of users we allow to be running at a time.
+    static final int MAX_RUNNING_USERS = 3;
+
     // How long to wait in getAssistContextExtras for the activity and foreground services
     // to respond with the result.
     static final int PENDING_ASSIST_EXTRAS_TIMEOUT = 500;
@@ -1271,8 +1277,6 @@
 
     final ActivityThread mSystemThread;
 
-    private UserManagerService mUserManager;
-
     private final class AppDeathRecipient implements IBinder.DeathRecipient {
         final ProcessRecord mApp;
         final int mPid;
@@ -1400,7 +1404,7 @@
                     boolean isBackground = (UserHandle.getAppId(proc.uid)
                             >= Process.FIRST_APPLICATION_UID
                             && proc.pid != MY_PID);
-                    for (int userId : mUserController.mCurrentProfileIds) {
+                    for (int userId : mUserController.getCurrentProfileIdsLocked()) {
                         isBackground &= (proc.userId != userId);
                     }
                     if (isBackground && !showBackground) {
@@ -1565,7 +1569,7 @@
                 break;
             }
             case START_USER_SWITCH_MSG: {
-                showUserSwitchDialog(msg.arg1, (String) msg.obj);
+                mUserController.showUserSwitchDialog(msg.arg1, (String) msg.obj);
                 break;
             }
             case DISMISS_DIALOG_MSG: {
@@ -3591,8 +3595,7 @@
 
     void enforceShellRestriction(String restriction, int userHandle) {
         if (Binder.getCallingUid() == Process.SHELL_UID) {
-            if (userHandle < 0
-                    || mUserManager.hasUserRestriction(restriction, userHandle)) {
+            if (userHandle < 0 || mUserController.hasUserRestriction(restriction, userHandle)) {
                 throw new SecurityException("Shell does not have permission to access user "
                         + userHandle);
             }
@@ -3854,8 +3857,8 @@
             Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
             int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
         enforceNotIsolatedCaller("startActivity");
-        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false, ALLOW_FULL_ONLY, "startActivity", null);
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, ALLOW_FULL_ONLY, "startActivity", null);
         // TODO: Switch to user app stacks here.
         return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
                 resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
@@ -3945,8 +3948,8 @@
             Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
             int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
         enforceNotIsolatedCaller("startActivityAndWait");
-        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false, ALLOW_FULL_ONLY, "startActivityAndWait", null);
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, ALLOW_FULL_ONLY, "startActivityAndWait", null);
         WaitResult res = new WaitResult();
         // TODO: Switch to user app stacks here.
         mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
@@ -3960,8 +3963,8 @@
             Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
             int startFlags, Configuration config, Bundle options, int userId) {
         enforceNotIsolatedCaller("startActivityWithConfig");
-        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false, ALLOW_FULL_ONLY, "startActivityWithConfig", null);
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, ALLOW_FULL_ONLY, "startActivityWithConfig", null);
         // TODO: Switch to user app stacks here.
         int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
                 resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
@@ -4018,8 +4021,8 @@
         if (session == null || interactor == null) {
             throw new NullPointerException("null session or interactor");
         }
-        userId = handleIncomingUser(callingPid, callingUid, userId,
-                false, ALLOW_FULL_ONLY, "startVoiceActivity", null);
+        userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
+                ALLOW_FULL_ONLY, "startVoiceActivity", null);
         // TODO: Switch to user app stacks here.
         return mStackSupervisor.startActivityMayWait(null, callingUid, callingPackage, intent,
                 resolvedType, session, interactor, null, null, 0, startFlags, profilerInfo, null,
@@ -4089,8 +4092,8 @@
                         if (debug) {
                             Slog.v(TAG, "Next matching activity: found current " + r.packageName
                                     + "/" + r.info.name);
-                            Slog.v(TAG, "Next matching activity: next is " + aInfo.packageName
-                                    + "/" + aInfo.name);
+                            Slog.v(TAG, "Next matching activity: next is " + ((aInfo == null)
+                                    ? "null" : aInfo.packageName + "/" + aInfo.name));
                         }
                         break;
                     }
@@ -4207,8 +4210,8 @@
             String resultWho, int requestCode, int startFlags, Bundle options, int userId,
             IActivityContainer container, TaskRecord inTask) {
 
-        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
 
         // TODO: Switch to user app stacks here.
         int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent,
@@ -4222,8 +4225,8 @@
             Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle options,
             int userId) {
         enforceNotIsolatedCaller("startActivities");
-        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false, ALLOW_FULL_ONLY, "startActivity", null);
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, ALLOW_FULL_ONLY, "startActivity", null);
         // TODO: Switch to user app stacks here.
         int ret = mStackSupervisor.startActivities(caller, -1, callingPackage, intents,
                 resolvedTypes, resultTo, options, userId);
@@ -4234,8 +4237,8 @@
             Intent[] intents, String[] resolvedTypes, IBinder resultTo,
             Bundle options, int userId) {
 
-        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
         // TODO: Switch to user app stacks here.
         int ret = mStackSupervisor.startActivities(null, uid, callingPackage, intents, resolvedTypes,
                 resultTo, options, userId);
@@ -5136,8 +5139,8 @@
         }
         int uid = Binder.getCallingUid();
         int pid = Binder.getCallingPid();
-        userId = handleIncomingUser(pid, uid,
-                userId, false, ALLOW_FULL_ONLY, "clearApplicationUserData", null);
+        userId = mUserController.handleIncomingUser(pid, uid, userId, false,
+                ALLOW_FULL_ONLY, "clearApplicationUserData", null);
         long callingId = Binder.clearCallingIdentity();
         try {
             IPackageManager pm = AppGlobals.getPackageManager();
@@ -5216,7 +5219,7 @@
             throw new SecurityException(msg);
         }
 
-        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, true, ALLOW_FULL_ONLY, "killBackgroundProcesses", null);
         long callingId = Binder.clearCallingIdentity();
         try {
@@ -5299,14 +5302,14 @@
             throw new SecurityException(msg);
         }
         final int callingPid = Binder.getCallingPid();
-        userId = handleIncomingUser(callingPid, Binder.getCallingUid(),
+        userId = mUserController.handleIncomingUser(callingPid, Binder.getCallingUid(),
                 userId, true, ALLOW_FULL_ONLY, "forceStopPackage", null);
         long callingId = Binder.clearCallingIdentity();
         try {
             IPackageManager pm = AppGlobals.getPackageManager();
             synchronized(this) {
                 int[] users = userId == UserHandle.USER_ALL
-                        ? getUsersLocked() : new int[] { userId };
+                        ? mUserController.getUsers() : new int[] { userId };
                 for (int user : users) {
                     int pkgUid = -1;
                     try {
@@ -5324,7 +5327,7 @@
                         Slog.w(TAG, "Failed trying to unstop package "
                                 + packageName + ": " + e);
                     }
-                    if (isUserRunningLocked(user, false)) {
+                    if (mUserController.isUserRunningLocked(user, false)) {
                         forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
                     }
                 }
@@ -6662,7 +6665,7 @@
         synchronized(this) {
             int callingUid = Binder.getCallingUid();
             int origUserId = userId;
-            userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
+            userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
                     type == ActivityManager.INTENT_SENDER_BROADCAST,
                     ALLOW_NON_FULL, "getIntentSender", null);
             if (origUserId == UserHandle.USER_CURRENT) {
@@ -7910,8 +7913,9 @@
     @Override
     public void grantUriPermissionFromOwner(IBinder token, int fromUid, String targetPkg, Uri uri,
             final int modeFlags, int sourceUserId, int targetUserId) {
-        targetUserId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                targetUserId, false, ALLOW_FULL_ONLY, "grantUriPermissionFromOwner", null);
+        targetUserId = mUserController.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), targetUserId, false, ALLOW_FULL_ONLY,
+                "grantUriPermissionFromOwner", null);
         synchronized(this) {
             UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
             if (owner == null) {
@@ -8362,6 +8366,9 @@
         rti.affiliatedTaskId = tr.mAffiliatedTaskId;
         rti.affiliatedTaskColor = tr.mAffiliatedTaskColor;
         rti.numActivities = 0;
+        if (tr.mBounds != null) {
+            rti.bounds = new Rect(tr.mBounds);
+        }
 
         ActivityRecord base = null;
         ActivityRecord top = null;
@@ -8415,7 +8422,7 @@
     @Override
     public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
         final int callingUid = Binder.getCallingUid();
-        userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
                 false, ALLOW_FULL_ONLY, "getRecentTasks", null);
 
         final boolean includeProfiles = (flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0;
@@ -8433,7 +8440,7 @@
 
             final Set<Integer> includedUsers;
             if (includeProfiles) {
-                includedUsers = getProfileIdsLocked(userId);
+                includedUsers = mUserController.getProfileIds(userId);
             } else {
                 includedUsers = new HashSet<>();
             }
@@ -8651,12 +8658,9 @@
                 // - a non-null bounds on a non-freeform (fullscreen OR docked) task moves
                 //   that task to freeform
                 // - otherwise the task is not moved
-                // Note it's not allowed to resize a home, docked, or pinned stack task.
                 int stackId = task.stack.mStackId;
-                if (stackId == HOME_STACK_ID || stackId == DOCKED_STACK_ID
-                        || stackId == PINNED_STACK_ID) {
-                    throw new IllegalArgumentException("trying to resizeTask on a "
-                            + "home or docked task");
+                if (!StackId.isTaskResizeAllowed(stackId)) {
+                    throw new IllegalArgumentException("resizeTask not allowed on task=" + task);
                 }
                 if (bounds == null && stackId == FREEFORM_WORKSPACE_STACK_ID) {
                     stackId = FULLSCREEN_WORKSPACE_STACK_ID;
@@ -9458,16 +9462,15 @@
         boolean checkedGrants = false;
         if (checkUser) {
             // Looking for cross-user grants before enforcing the typical cross-users permissions
-            int tmpTargetUserId = unsafeConvertIncomingUserLocked(userId);
+            int tmpTargetUserId = mUserController.unsafeConvertIncomingUserLocked(userId);
             if (tmpTargetUserId != UserHandle.getUserId(callingUid)) {
                 if (checkAuthorityGrants(callingUid, cpi, tmpTargetUserId, checkUser)) {
                     return null;
                 }
                 checkedGrants = true;
             }
-            userId = handleIncomingUser(callingPid, callingUid, userId,
-                    false, ALLOW_NON_FULL,
-                    "checkContentProviderPermissionLocked " + cpi.authority, null);
+            userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
+                    ALLOW_NON_FULL, "checkContentProviderPermissionLocked " + cpi.authority, null);
             if (userId != tmpTargetUserId) {
                 // When we actually went to determine the final targer user ID, this ended
                 // up different than our initial check for the authority.  This is because
@@ -9806,7 +9809,7 @@
 
                 // Make sure that the user who owns this provider is running.  If not,
                 // we don't want to allow it to run.
-                if (!isUserRunningLocked(userId, false)) {
+                if (!mUserController.isUserRunningLocked(userId, false)) {
                     Slog.w(TAG, "Unable to launch app "
                             + cpi.applicationInfo.packageName + "/"
                             + cpi.applicationInfo.uid + " for provider "
@@ -9992,8 +9995,8 @@
             String name, int userId, IBinder token) {
         enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY,
             "Do not have permission in call getContentProviderExternal()");
-        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false, ALLOW_FULL_ONLY, "getContentProvider", null);
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, ALLOW_FULL_ONLY, "getContentProvider", null);
         return getContentProviderExternalUnchecked(name, token, userId);
     }
 
@@ -10305,7 +10308,7 @@
         long ident = 0;
         boolean clearedIdentity = false;
         synchronized (this) {
-            userId = unsafeConvertIncomingUserLocked(userId);
+            userId = mUserController.unsafeConvertIncomingUserLocked(userId);
         }
         if (canClearIdentity(callingPid, callingUid, userId)) {
             clearedIdentity = true;
@@ -10975,7 +10978,7 @@
     public boolean isAssistDataAllowedOnCurrentActivity() {
         int userId;
         synchronized (this) {
-            userId = mUserController.mCurrentUserId;
+            userId = mUserController.getCurrentUserIdLocked();
             ActivityRecord activity = getFocusedStack().topActivity();
             if (activity == null) {
                 return false;
@@ -11890,7 +11893,7 @@
         }
 
         // TODO: can we still do this with per user encryption?
-        final int[] users = getUsersLocked();
+        final int[] users = mUserController.getUsers();
         if (users.length <= 0) {
             return false;
         }
@@ -11920,8 +11923,7 @@
             mUserController.updateCurrentProfileIdsLocked();
 
             mRecentTasks.clear();
-            mRecentTasks.addAll(mTaskPersister.restoreTasksLocked(
-                    getUserManagerLocked().getUserIds()));
+            mRecentTasks.addAll(mTaskPersister.restoreTasksLocked(mUserController.getUserIds()));
             mRecentTasks.cleanupLocked(UserHandle.USER_ALL);
             mTaskPersister.startPersisting();
 
@@ -12026,7 +12028,7 @@
         loadResourcesOnSystemReady();
         final int currentUserId;
         synchronized (this) {
-            currentUserId = mUserController.mCurrentUserId;
+            currentUserId = mUserController.getCurrentUserIdLocked();
             readGrantedUriPermissionsLocked();
         }
 
@@ -12110,7 +12112,7 @@
                 Binder.restoreCallingIdentity(ident);
             }
             mStackSupervisor.resumeTopActivitiesLocked();
-            sendUserSwitchBroadcastsLocked(-1, currentUserId);
+            mUserController.sendUserSwitchBroadcastsLocked(-1, currentUserId);
         }
     }
 
@@ -12310,7 +12312,7 @@
         // launching the report UI under a different user.
         app.errorReportReceiver = null;
 
-        for (int userId : mUserController.mCurrentProfileIds) {
+        for (int userId : mUserController.getCurrentProfileIdsLocked()) {
             if (app.userId == userId) {
                 app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
                         mContext, app.info.packageName, app.info.flags);
@@ -16031,97 +16033,10 @@
     @Override
     public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
             boolean requireFull, String name, String callerPackage) {
-        return handleIncomingUser(callingPid, callingUid, userId, allowAll,
+        return mUserController.handleIncomingUser(callingPid, callingUid, userId, allowAll,
                 requireFull ? ALLOW_FULL_ONLY : ALLOW_NON_FULL, name, callerPackage);
     }
 
-    int unsafeConvertIncomingUserLocked(int userId) {
-        return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
-                ? mUserController.mCurrentUserId : userId;
-    }
-
-    int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
-            int allowMode, String name, String callerPackage) {
-        final int callingUserId = UserHandle.getUserId(callingUid);
-        if (callingUserId == userId) {
-            return userId;
-        }
-
-        // Note that we may be accessing mCurrentUserId outside of a lock...
-        // shouldn't be a big deal, if this is being called outside
-        // of a locked context there is intrinsically a race with
-        // the value the caller will receive and someone else changing it.
-        // We assume that USER_CURRENT_OR_SELF will use the current user; later
-        // we will switch to the calling user if access to the current user fails.
-        int targetUserId = unsafeConvertIncomingUserLocked(userId);
-
-        if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
-            final boolean allow;
-            if (checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
-                    callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
-                // If the caller has this permission, they always pass go.  And collect $200.
-                allow = true;
-            } else if (allowMode == ALLOW_FULL_ONLY) {
-                // We require full access, sucks to be you.
-                allow = false;
-            } else if (checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
-                    callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
-                // If the caller does not have either permission, they are always doomed.
-                allow = false;
-            } else if (allowMode == ALLOW_NON_FULL) {
-                // We are blanket allowing non-full access, you lucky caller!
-                allow = true;
-            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
-                // We may or may not allow this depending on whether the two users are
-                // in the same profile.
-                allow = mUserController.isSameProfileGroup(callingUserId, targetUserId);
-            } else {
-                throw new IllegalArgumentException("Unknown mode: " + allowMode);
-            }
-            if (!allow) {
-                if (userId == UserHandle.USER_CURRENT_OR_SELF) {
-                    // In this case, they would like to just execute as their
-                    // owner user instead of failing.
-                    targetUserId = callingUserId;
-                } else {
-                    StringBuilder builder = new StringBuilder(128);
-                    builder.append("Permission Denial: ");
-                    builder.append(name);
-                    if (callerPackage != null) {
-                        builder.append(" from ");
-                        builder.append(callerPackage);
-                    }
-                    builder.append(" asks to run as user ");
-                    builder.append(userId);
-                    builder.append(" but is calling from user ");
-                    builder.append(UserHandle.getUserId(callingUid));
-                    builder.append("; this requires ");
-                    builder.append(INTERACT_ACROSS_USERS_FULL);
-                    if (allowMode != ALLOW_FULL_ONLY) {
-                        builder.append(" or ");
-                        builder.append(INTERACT_ACROSS_USERS);
-                    }
-                    String msg = builder.toString();
-                    Slog.w(TAG, msg);
-                    throw new SecurityException(msg);
-                }
-            }
-        }
-        if (!allowAll && targetUserId < 0) {
-            throw new IllegalArgumentException(
-                    "Call does not support special user #" + targetUserId);
-        }
-        // Check shell permission
-        if (callingUid == Process.SHELL_UID && targetUserId >= UserHandle.USER_SYSTEM) {
-            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES,
-                    targetUserId)) {
-                throw new SecurityException("Shell does not have permission to access user "
-                        + targetUserId + "\n " + Debug.getCallers(3));
-            }
-        }
-        return targetUserId;
-    }
-
     boolean isSingleton(String componentProcessName, ApplicationInfo aInfo,
             String className, int flags) {
         boolean result = false;
@@ -16434,8 +16349,8 @@
                 callingPid = Binder.getCallingPid();
             }
 
-            userId = handleIncomingUser(callingPid, callingUid, userId,
-                    true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
+            userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+                    ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
 
             Iterator<String> actions = filter.actionsIterator();
             if (actions == null) {
@@ -16625,8 +16540,8 @@
             for (int user : users) {
                 // Skip users that have Shell restrictions
                 if (callingUid == Process.SHELL_UID
-                        && getUserManagerLocked().hasUserRestriction(
-                                UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
+                        && mUserController.hasUserRestriction(
+                        UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
                     continue;
                 }
                 List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
@@ -16715,14 +16630,14 @@
             Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
         }
 
-        userId = handleIncomingUser(callingPid, callingUid, userId,
-                true, ALLOW_NON_FULL, "broadcast", callerPackage);
+        userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+                ALLOW_NON_FULL, "broadcast", callerPackage);
 
         // Make sure that the user who is receiving this broadcast is running.
         // If not, we will just skip it. Make an exception for shutdown broadcasts
         // and upgrade steps.
 
-        if (userId != UserHandle.USER_ALL && !isUserRunningLocked(userId, false)) {
+        if (userId != UserHandle.USER_ALL && !mUserController.isUserRunningLocked(userId, false)) {
             if ((callingUid != Process.SYSTEM_UID
                     || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
                     && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
@@ -17027,9 +16942,8 @@
         if (intent.getComponent() == null) {
             if (userId == UserHandle.USER_ALL && callingUid == Process.SHELL_UID) {
                 // Query one target user at a time, excluding shell-restricted users
-                UserManagerService ums = getUserManagerLocked();
                 for (int i = 0; i < users.length; i++) {
-                    if (ums.hasUserRestriction(
+                    if (mUserController.hasUserRestriction(
                             UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
                         continue;
                     }
@@ -17247,7 +17161,7 @@
             throw new IllegalArgumentException("File descriptors passed in Intent");
         }
 
-        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, true, ALLOW_NON_FULL, "removeStickyBroadcast", null);
 
         synchronized(this) {
@@ -17331,7 +17245,7 @@
             IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection,
             int userId, String abiOverride) {
         enforceNotIsolatedCaller("startInstrumentation");
-        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, false, ALLOW_FULL_ONLY, "startInstrumentation", null);
         // Refuse possible leaked file descriptors
         if (arguments != null && arguments.hasFileDescriptors()) {
@@ -17597,10 +17511,11 @@
             Binder.restoreCallingIdentity(origId);
         }
     }
+
     void updateUserConfigurationLocked() {
         Configuration configuration = new Configuration(mConfiguration);
         Settings.System.getConfigurationForUser(mContext.getContentResolver(), configuration,
-                mUserController.mCurrentUserId);
+                mUserController.getCurrentUserIdLocked());
         updateConfigurationLocked(configuration, null, false);
     }
 
@@ -17649,7 +17564,7 @@
                 mConfiguration = newConfig;
                 Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
                 mUsageStatsService.reportConfigurationChange(newConfig,
-                        mUserController.mCurrentUserId);
+                        mUserController.getCurrentUserIdLocked());
                 //mUsageStatsService.noteStartConfig(newConfig);
 
                 final Configuration configCopy = new Configuration(mConfiguration);
@@ -18918,7 +18833,8 @@
         }
     }
 
-    private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now) {
+    private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
+            long nowElapsed) {
         boolean success = true;
 
         if (app.curRawAdj != app.setRawAdj) {
@@ -19022,7 +18938,7 @@
         if (app.setProcState != app.curProcState) {
             if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
                     "Proc state change of " + app.processName
-                    + " to " + app.curProcState);
+                            + " to " + app.curProcState);
             boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE;
             boolean curImportant = app.curProcState < ActivityManager.PROCESS_STATE_SERVICE;
             if (setImportant && !curImportant) {
@@ -19033,14 +18949,14 @@
                 BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
                 synchronized (stats) {
                     app.lastWakeTime = stats.getProcessWakeTime(app.info.uid,
-                            app.pid, SystemClock.elapsedRealtime());
+                            app.pid, nowElapsed);
                 }
                 app.lastCpuTime = app.curCpuTime;
 
             }
             // Inform UsageStats of important process state change
             // Must be called before updating setProcState
-            maybeUpdateUsageStatsLocked(app);
+            maybeUpdateUsageStatsLocked(app, nowElapsed);
 
             app.setProcState = app.curProcState;
             if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
@@ -19051,6 +18967,11 @@
             } else {
                 app.procStateChanged = true;
             }
+        } else if (app.reportedInteraction && (nowElapsed-app.interactionEventTime)
+                > USAGE_STATS_INTERACTION_INTERVAL) {
+            // For apps that sit around for a long time in the interactive state, we need
+            // to report this at least once a day so they don't go idle.
+            maybeUpdateUsageStatsLocked(app, nowElapsed);
         }
 
         if (changes != 0) {
@@ -19133,7 +19054,7 @@
             String authority) {
         if (app == null) return;
         if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-            UserState userState = mUserController.getStartedUserState(app.userId);
+            UserState userState = mUserController.getStartedUserStateLocked(app.userId);
             if (userState == null) return;
             final long now = SystemClock.elapsedRealtime();
             Long lastReported = userState.mProviderLastReportedFg.get(authority);
@@ -19145,7 +19066,7 @@
         }
     }
 
-    private void maybeUpdateUsageStatsLocked(ProcessRecord app) {
+    private void maybeUpdateUsageStatsLocked(ProcessRecord app, long nowElapsed) {
         if (DEBUG_USAGE_STATS) {
             Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList())
                     + "] state changes: old = " + app.setProcState + ", new = "
@@ -19162,19 +19083,20 @@
             isInteraction = true;
             app.fgInteractionTime = 0;
         } else if (app.curProcState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
-            final long now = SystemClock.elapsedRealtime();
             if (app.fgInteractionTime == 0) {
-                app.fgInteractionTime = now;
+                app.fgInteractionTime = nowElapsed;
                 isInteraction = false;
             } else {
-                isInteraction = now > app.fgInteractionTime + SERVICE_USAGE_INTERACTION_TIME;
+                isInteraction = nowElapsed > app.fgInteractionTime + SERVICE_USAGE_INTERACTION_TIME;
             }
         } else {
             isInteraction = app.curProcState
                     <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
             app.fgInteractionTime = 0;
         }
-        if (isInteraction && !app.reportedInteraction) {
+        if (isInteraction && (!app.reportedInteraction
+                || (nowElapsed-app.interactionEventTime) > USAGE_STATS_INTERACTION_INTERVAL)) {
+            app.interactionEventTime = nowElapsed;
             String[] packages = app.getPackageList();
             if (packages != null) {
                 for (int i = 0; i < packages.length; i++) {
@@ -19184,6 +19106,9 @@
             }
         }
         app.reportedInteraction = isInteraction;
+        if (!isInteraction) {
+            app.interactionEventTime = 0;
+        }
     }
 
     private final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
@@ -19206,7 +19131,7 @@
 
         computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
 
-        return applyOomAdjLocked(app, doingAll, now);
+        return applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
     }
 
     final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
@@ -19298,6 +19223,7 @@
         final ActivityRecord TOP_ACT = resumedAppLocked();
         final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;
         final long now = SystemClock.uptimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
         final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
         final int N = mLruProcesses.size();
 
@@ -19426,7 +19352,7 @@
                     }
                 }
 
-                applyOomAdjLocked(app, true, now);
+                applyOomAdjLocked(app, true, now, nowElapsed);
 
                 // Count the number of process types.
                 switch (app.curProcState) {
@@ -19855,7 +19781,7 @@
     }
 
     private ProcessRecord findProcessLocked(String process, int userId, String callName) {
-        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, true, ALLOW_FULL_ONLY, callName, null);
         ProcessRecord proc = null;
         try {
@@ -20016,31 +19942,12 @@
         return mUserController.startUser(userId, /* foreground */ false);
     }
 
-    /**
-     * Start user, if its not already running, and bring it to foreground.
-     */
-    boolean startUserInForeground(final int userId, Dialog dlg) {
-        boolean result = mUserController.startUser(userId, /* foreground */ true);
-        dlg.dismiss();
-        return result;
-    }
-
-    private Set<Integer> getProfileIdsLocked(int userId) {
-        Set<Integer> userIds = new HashSet<Integer>();
-        final List<UserInfo> profiles = getUserManagerLocked().getProfiles(
-                userId, false /* enabledOnly */);
-        for (UserInfo user : profiles) {
-            userIds.add(Integer.valueOf(user.id));
-        }
-        return userIds;
-    }
-
     @Override
     public boolean switchUser(final int userId) {
         enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
         String userName;
         synchronized (this) {
-            UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
+            UserInfo userInfo = mUserController.getUserInfo(userId);
             if (userInfo == null) {
                 Slog.w(TAG, "No user info for user #" + userId);
                 return false;
@@ -20050,68 +19957,13 @@
                 return false;
             }
             userName = userInfo.name;
-            mUserController.mTargetUserId = userId;
+            mUserController.setTargetUserIdLocked(userId);
         }
         mUiHandler.removeMessages(START_USER_SWITCH_MSG);
         mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName));
         return true;
     }
 
-    private void showUserSwitchDialog(int userId, String userName) {
-        // The dialog will show and then initiate the user switch by calling startUserInForeground
-        Dialog d = new UserSwitchingDialog(this, mContext, userId, userName,
-                true /* above system */);
-        d.show();
-    }
-
-    void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
-        long ident = Binder.clearCallingIdentity();
-        try {
-            Intent intent;
-            if (oldUserId >= 0) {
-                // Send USER_BACKGROUND broadcast to all profiles of the outgoing user
-                List<UserInfo> profiles = mUserManager.getProfiles(oldUserId, false);
-                int count = profiles.size();
-                for (int i = 0; i < count; i++) {
-                    int profileUserId = profiles.get(i).id;
-                    intent = new Intent(Intent.ACTION_USER_BACKGROUND);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                            | Intent.FLAG_RECEIVER_FOREGROUND);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
-                    broadcastIntentLocked(null, null, intent,
-                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                            null, false, false, MY_PID, Process.SYSTEM_UID, profileUserId);
-                }
-            }
-            if (newUserId >= 0) {
-                // Send USER_FOREGROUND broadcast to all profiles of the incoming user
-                List<UserInfo> profiles = mUserManager.getProfiles(newUserId, false);
-                int count = profiles.size();
-                for (int i = 0; i < count; i++) {
-                    int profileUserId = profiles.get(i).id;
-                    intent = new Intent(Intent.ACTION_USER_FOREGROUND);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                            | Intent.FLAG_RECEIVER_FOREGROUND);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
-                    broadcastIntentLocked(null, null, intent,
-                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                            null, false, false, MY_PID, Process.SYSTEM_UID, profileUserId);
-                }
-                intent = new Intent(Intent.ACTION_USER_SWITCHED);
-                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                        | Intent.FLAG_RECEIVER_FOREGROUND);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
-                broadcastIntentLocked(null, null, intent,
-                        null, null, 0, null, null,
-                        new String[] {android.Manifest.permission.MANAGE_USERS},
-                        AppOpsManager.OP_NONE, null, false, false, MY_PID, Process.SYSTEM_UID,
-                        UserHandle.USER_ALL);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
     void scheduleStartProfilesLocked() {
         if (!mHandler.hasMessages(START_PROFILES_MSG)) {
             mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
@@ -20145,22 +19997,10 @@
             throw new SecurityException(msg);
         }
         synchronized (this) {
-            return isUserRunningLocked(userId, orStopped);
+            return mUserController.isUserRunningLocked(userId, orStopped);
         }
     }
 
-    boolean isUserRunningLocked(int userId, boolean orStopped) {
-        UserState state = mUserController.getStartedUserState(userId);
-        if (state == null) {
-            return false;
-        }
-        if (orStopped) {
-            return true;
-        }
-        return state.mState != UserState.STATE_STOPPING
-                && state.mState != UserState.STATE_SHUTDOWN;
-    }
-
     @Override
     public int[] getRunningUserIds() {
         if (checkCallingPermission(INTERACT_ACROSS_USERS)
@@ -20187,19 +20027,6 @@
         mUserController.unregisterUserSwitchObserver(observer);
     }
 
-    int[] getUsersLocked() {
-        UserManagerService ums = getUserManagerLocked();
-        return ums != null ? ums.getUserIds() : new int[] { 0 };
-    }
-
-    UserManagerService getUserManagerLocked() {
-        if (mUserManager == null) {
-            IBinder b = ServiceManager.getService(Context.USER_SERVICE);
-            mUserManager = (UserManagerService)IUserManager.Stub.asInterface(b);
-        }
-        return mUserManager;
-    }
-
     private int applyUserId(int uid, int userId) {
         return UserHandle.getUid(userId, uid);
     }
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index b5f424d..ee9aa11 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -16,14 +16,11 @@
 
 package com.android.server.am;
 
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FIRST_STATIC_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
-import static android.app.ActivityManager.LAST_STATIC_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
 
 import static com.android.server.am.ActivityManagerDebugConfig.*;
@@ -49,6 +46,7 @@
 
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.ActivityManager.StackId;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IActivityController;
@@ -255,9 +253,6 @@
 
     private final LaunchingTaskPositioner mTaskPositioner;
 
-    // If the bounds of task contained in this stack should be persisted across power cycles.
-    final boolean mPersistTaskBounds;
-
     static final int PAUSE_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 1;
     static final int DESTROY_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 2;
     static final int LAUNCH_TICK_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 3;
@@ -367,11 +362,10 @@
         mHandler = new ActivityStackHandler(mService.mHandler.getLooper());
         mWindowManager = mService.mWindowManager;
         mStackId = activityContainer.mStackId;
-        mCurrentUser = mService.mUserController.mCurrentUserId;
+        mCurrentUser = mService.mUserController.getCurrentUserIdLocked();
         mRecentTasks = recentTasks;
         mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
                 ? new LaunchingTaskPositioner() : null;
-        mPersistTaskBounds = mStackId != DOCKED_STACK_ID && mStackId != PINNED_STACK_ID;
     }
 
     void attachDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
@@ -1365,7 +1359,7 @@
             }
         }
 
-        if (mStackId >= FIRST_STATIC_STACK_ID && mStackId <= LAST_STATIC_STACK_ID) {
+        if (StackId.isStaticStack(mStackId)) {
             // Visibility of any static stack should have been determined by the conditions above.
             return false;
         }
@@ -1377,9 +1371,7 @@
                 continue;
             }
 
-            if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID
-                    || stack.mStackId == HOME_STACK_ID
-                    || stack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+            if (!StackId.isDynamicStacksVisibleBehindAllowed(stack.mStackId)) {
                 // These stacks can't have any dynamic stacks visible behind them.
                 return false;
             }
@@ -1464,29 +1456,35 @@
                     }
 
                     if (r.app == null || r.app.thread == null) {
-                        // This activity needs to be visible, but isn't even running...
-                        // get it started and resume if no other stack in this stack is resumed.
-                        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
-                                "Start and freeze screen for " + r);
-                        if (r != starting) {
-                            r.startFreezingScreenLocked(r.app, configChanges);
-                        }
-                        if (!r.visible || r.mLaunchTaskBehind) {
+                        // We need to make sure the app is running if it's the top, or it is
+                        // just made visible from invisible.
+                        // If the app is already visible, it must have died while it was visible.
+                        // In this case, we'll show the dead window but will not restart the app.
+                        // Otherwise we could end up thrashing.
+                        if (r == top || !r.visible) {
+                            // This activity needs to be visible, but isn't even running...
+                            // get it started and resume if no other stack in this stack is resumed.
                             if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
-                                    "Starting and making visible: " + r);
-                            setVisible(r, true);
-                        }
-                        if (r != starting) {
-                            mStackSupervisor.startSpecificActivityLocked(
-                                    r, noStackActivityResumed, false);
-                            if (activityNdx >= activities.size()) {
-                                // Record may be removed if its process needs to restart.
-                                activityNdx = activities.size() - 1;
-                            } else {
-                                noStackActivityResumed = false;
+                                    "Start and freeze screen for " + r);
+                            if (r != starting) {
+                                r.startFreezingScreenLocked(r.app, configChanges);
+                            }
+                            if (!r.visible || r.mLaunchTaskBehind) {
+                                if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+                                        "Starting and making visible: " + r);
+                                setVisible(r, true);
+                            }
+                            if (r != starting) {
+                                mStackSupervisor.startSpecificActivityLocked(
+                                        r, noStackActivityResumed, false);
+                                if (activityNdx >= activities.size()) {
+                                    // Record may be removed if its process needs to restart.
+                                    activityNdx = activities.size() - 1;
+                                } else {
+                                    noStackActivityResumed = false;
+                                }
                             }
                         }
-
                     } else if (r.visible) {
                         // If this activity is already visible, then there is nothing
                         // else to do here.
@@ -2797,8 +2795,7 @@
             ActivityRecord next = topRunningActivityLocked();
             final String myReason = reason + " adjustFocus";
             if (next != r) {
-                if (next != null && (mStackId == FREEFORM_WORKSPACE_STACK_ID
-                        || mStackId == DOCKED_STACK_ID || mStackId == PINNED_STACK_ID)) {
+                if (next != null && StackId.keepFocusInStackIfPossible(mStackId)) {
                     // For freeform, docked, and pinned stacks we always keep the focus within the
                     // stack as long as there is a running activity in the stack that we can adjust
                     // focus to.
@@ -3740,10 +3737,13 @@
                         // Don't currently have state for the activity, or
                         // it is finishing -- always remove it.
                         remove = true;
-                    } else if (r.launchCount > 2 &&
-                            r.lastLaunchTime > (SystemClock.uptimeMillis()-60000)) {
+                    } else if (!r.visible && r.launchCount > 2 &&
+                            r.lastLaunchTime > (SystemClock.uptimeMillis() - 60000)) {
                         // We have launched this activity too many times since it was
                         // able to run, so give up and remove it.
+                        // (Note if the activity is visible, we don't remove the record.
+                        // We leave the dead window on the screen but the process will
+                        // not be restarted unless user explicitly tap on it.)
                         remove = true;
                     } else {
                         // The process may be gone, but the activity lives on!
@@ -3773,7 +3773,11 @@
                         if (DEBUG_APP) Slog.v(TAG_APP,
                                 "Clearing app during removeHistory for activity " + r);
                         r.app = null;
-                        r.nowVisible = false;
+                        // Set nowVisible to previous visible state. If the app was visible while
+                        // it died, we leave the dead window on screen so it's basically visible.
+                        // This is needed when user later tap on the dead window, we need to stop
+                        // other apps when user transfers focus to the restarted activity.
+                        r.nowVisible = r.visible;
                         if (!r.haveState) {
                             if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE,
                                     "App died, clearing saved state 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 1cd71a1..f262ec9 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -18,6 +18,15 @@
 
 import static android.Manifest.permission.START_ANY_ACTIVITY;
 import static android.app.ActivityManager.*;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
+import static android.app.ActivityManager.StackId.FIRST_STATIC_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.LAST_STATIC_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -40,6 +49,7 @@
 import android.Manifest;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
@@ -364,13 +374,26 @@
         final ActivityRecord sourceRecord;
         final int startFlags;
         final ActivityStack stack;
+        final ProcessRecord callerApp;
 
         PendingActivityLaunch(ActivityRecord _r, ActivityRecord _sourceRecord,
-                int _startFlags, ActivityStack _stack) {
+                int _startFlags, ActivityStack _stack, ProcessRecord _callerApp) {
             r = _r;
             sourceRecord = _sourceRecord;
             startFlags = _startFlags;
             stack = _stack;
+            callerApp = _callerApp;
+        }
+
+        void sendErrorResult(String message) {
+            try {
+                if (callerApp.thread != null) {
+                    callerApp.thread.scheduleCrash(message);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Exception scheduling crash of failed "
+                        + "activity launcher sourceRecord=" + sourceRecord, e);
+            }
         }
     }
 
@@ -554,8 +577,8 @@
      * @param id Id of the task we would like returned.
      * @param restoreFromRecents If the id was not in the active list, but was found in recents,
      *                           restore the task from recents to the active list.
-     * @param stackId The stack to restore the task to (default launch stack will be used
-     *                if stackId is {@link android.app.ActivityManager#INVALID_STACK_ID}).
+     * @param stackId The stack to restore the task to (default launch stack will be used if
+     *                stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}).
      */
     TaskRecord anyTaskForIdLocked(int id, boolean restoreFromRecents, int stackId) {
         int numDisplays = mActivityDisplays.size();
@@ -1668,8 +1691,8 @@
                 || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {
             if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,
                     realCallingPid, realCallingUid, "Activity start")) {
-                PendingActivityLaunch pal =
-                        new PendingActivityLaunch(r, sourceRecord, startFlags, stack);
+                PendingActivityLaunch pal =  new PendingActivityLaunch(r, 
+                        sourceRecord, startFlags, stack, callerApp);
                 mPendingActivityLaunches.add(pal);
                 ActivityOptions.abort(options);
                 return ActivityManager.START_SWITCHES_CANCELED;
@@ -1820,8 +1843,7 @@
         final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks;
         for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             stack = homeDisplayStacks.get(stackNdx);
-            final boolean isDynamicStack = stack.mStackId >= FIRST_DYNAMIC_STACK_ID;
-            if (isDynamicStack) {
+            if (!StackId.isStaticStack(stack.mStackId)) {
                 if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
                         "computeStackFocus: Setting focused stack=" + stack);
                 return stack;
@@ -2536,9 +2558,10 @@
 
             try {
                 startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags,
-                                             doResume && mPendingActivityLaunches.isEmpty(), null, null);
+                        doResume && mPendingActivityLaunches.isEmpty(), null, null);
             } catch (Exception e) {
-                Slog.w(TAG, "Exception during pending activity launch pal=" + pal, e);
+                Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e);
+                pal.sendErrorResult(e.getMessage());
             }
         }
     }
@@ -2863,25 +2886,25 @@
             return;
         }
 
-        int stackId = task.stack.mStackId;
         if (task.mResizeable && options != null) {
             ActivityOptions opts = new ActivityOptions(options);
             if (opts.hasBounds()) {
                 Rect bounds = opts.getBounds();
                 task.updateOverrideConfiguration(bounds);
+                final int stackId = task.getLaunchStackId();
+                if (stackId != task.stack.mStackId) {
+                    moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, reason);
+                    // moveTaskToStackUncheckedLocked() should already placed the task on top,
+                    // still need moveTaskToFrontLocked() below for any transition settings.
+                }
+                // WM resizeTask must be done after the task is moved to the correct stack,
+                // because Task's setBounds() also updates dim layer's bounds, but that has
+                // dependency on the stack.
                 mWindowManager.resizeTask(task.taskId, bounds, task.mOverrideConfig,
                         false /*relayout*/, false /*forced*/);
-                stackId = task.getLaunchStackId();
             }
         }
 
-        if (stackId != task.stack.mStackId) {
-            moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, reason);
-
-            // moveTaskToStackUncheckedLocked() should already placed the task on top,
-            // still need moveTaskToFrontLocked() below for any transition settings.
-        }
-
         final ActivityRecord r = task.getTopActivity();
         task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
                 r == null ? null : r.appTimeTracker, reason);
@@ -2899,15 +2922,14 @@
         if (activityContainer != null) {
             return activityContainer.mStack;
         }
-        if (!createStaticStackIfNeeded
-                || (stackId < FIRST_STATIC_STACK_ID || stackId > LAST_STATIC_STACK_ID)) {
+        if (!createStaticStackIfNeeded || !StackId.isStaticStack(stackId)) {
             return null;
         }
         return createStackOnDisplay(stackId, Display.DEFAULT_DISPLAY, createOnTop);
     }
 
     ArrayList<ActivityStack> getStacks() {
-        ArrayList<ActivityStack> allStacks = new ArrayList<ActivityStack>();
+        ArrayList<ActivityStack> allStacks = new ArrayList<>();
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
             allStacks.addAll(mActivityDisplays.valueAt(displayNdx).mStacks);
         }
@@ -3025,7 +3047,7 @@
                     // In this case we make all other static stacks fullscreen and move all
                     // docked stack tasks to the fullscreen stack.
                     for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
-                        if (i != DOCKED_STACK_ID && getStack(i) != null) {
+                        if (StackId.isResizeableByDockedStack(i) && getStack(i) != null) {
                             resizeStackLocked(i, null, preserveWindows, true);
                         }
                     }
@@ -3046,7 +3068,7 @@
                     mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect);
 
                     for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
-                        if (i != DOCKED_STACK_ID) {
+                        if (StackId.isResizeableByDockedStack(i)) {
                             ActivityStack otherStack = getStack(i);
                             if (otherStack != null) {
                                 resizeStackLocked(i, tempRect, PRESERVE_WINDOWS, true);
@@ -3190,7 +3212,7 @@
      * Restores a recent task to a stack
      * @param task The recent task to be restored.
      * @param stackId The stack to restore the task to (default launch stack will be used
-     *                if stackId is {@link android.app.ActivityManager#INVALID_STACK_ID}).
+     *                if stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}).
      * @return true if the task has been restored successfully.
      */
     private boolean restoreRecentTaskLocked(TaskRecord task, int stackId) {
@@ -3275,8 +3297,7 @@
             Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
             return;
         }
-        if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID
-                || stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+        if (StackId.preserveWindowOnTaskMove(stackId)) {
             // We are about to relaunch the activity because its configuration changed due to
             // being maximized, i.e. size change. The activity will first remove the old window
             // and then add a new one. This call will tell window manager about this, so it can
@@ -4664,7 +4685,7 @@
         @Override
         public final int startActivity(Intent intent) {
             mService.enforceNotIsolatedCaller("ActivityContainer.startActivity");
-            final int userId = mService.handleIncomingUser(Binder.getCallingPid(),
+            final int userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(),
                     Binder.getCallingUid(), mCurrentUser, false,
                     ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null);
 
@@ -4690,7 +4711,7 @@
                 throw new IllegalArgumentException("Bad PendingIntent object");
             }
 
-            final int userId = mService.handleIncomingUser(Binder.getCallingPid(),
+            final int userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(),
                     Binder.getCallingUid(), mCurrentUser, false,
                     ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null);
 
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 6ed880e..8039072 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -248,7 +248,7 @@
                 boolean sendFinish = finishedReceiver != null;
                 int userId = key.userId;
                 if (userId == UserHandle.USER_CURRENT) {
-                    userId = owner.mUserController.getCurrentUserIdLocked();
+                    userId = owner.mUserController.getCurrentOrTargetUserIdLocked();
                 }
                 switch (key.type) {
                     case ActivityManager.INTENT_SENDER_ACTIVITY:
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 08203c55b..b77eec8 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -114,6 +114,7 @@
     boolean killed;             // True once we know the process has been killed
     boolean procStateChanged;   // Keep track of whether we changed 'setAdj'.
     boolean reportedInteraction;// Whether we have told usage stats about it being an interaction
+    long interactionEventTime;  // The time we sent the last interaction event
     long fgInteractionTime;     // When we became foreground for interaction purposes
     String waitingToKill;       // Process is waiting to be killed when in the bg, and reason
     IBinder forcingToForeground;// Token that is forcing this process to be foreground
@@ -297,6 +298,10 @@
         if (reportedInteraction || fgInteractionTime != 0) {
             pw.print(prefix); pw.print("reportedInteraction=");
             pw.print(reportedInteraction);
+            if (interactionEventTime != 0) {
+                pw.print(" time=");
+                TimeUtils.formatDuration(interactionEventTime, SystemClock.elapsedRealtime(), pw);
+            }
             if (fgInteractionTime != 0) {
                 pw.print(" fgInteractionTime=");
                 TimeUtils.formatDuration(fgInteractionTime, SystemClock.elapsedRealtime(), pw);
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index edd16ef..862a973 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -108,7 +108,7 @@
 
         final IPackageManager pm = AppGlobals.getPackageManager();
         final int[] users = (userId == UserHandle.USER_ALL)
-                ? mService.getUsersLocked() : new int[] { userId };
+                ? mService.mUserController.getUsers() : new int[] { userId };
         for (int userIdx = 0; userIdx < users.length; userIdx++) {
             final int user = users[userIdx];
             recentsCount = size() - 1;
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 120b40c..5fd213f 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -16,11 +16,9 @@
 
 package com.android.server.am;
 
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
@@ -35,6 +33,7 @@
 
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.TaskThumbnail;
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityOptions;
@@ -1193,14 +1192,14 @@
 
         mFullscreen = bounds == null;
         if (mFullscreen) {
-            if (mBounds != null && stack.mPersistTaskBounds) {
+            if (mBounds != null && StackId.persistTaskBounds(stack.mStackId)) {
                 mLastNonFullscreenBounds = mBounds;
             }
             mBounds = null;
             mOverrideConfig = Configuration.EMPTY;
         } else {
             mBounds = new Rect(bounds);
-            if (stack == null || stack.mPersistTaskBounds) {
+            if (stack == null || StackId.persistTaskBounds(stack.mStackId)) {
                 mLastNonFullscreenBounds = mBounds;
             }
 
@@ -1244,7 +1243,7 @@
                 || stackId == HOME_STACK_ID
                 || stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
             return (mResizeable && stack != null) ? stack.mBounds : null;
-        } else if (!stack.mPersistTaskBounds) {
+        } else if (!StackId.persistTaskBounds(stackId)) {
             return stack.mBounds;
         }
         return mLastNonFullscreenBounds;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index ff74d83..4085489 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -18,11 +18,17 @@
 
 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;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.ALLOW_FULL_ONLY;
+import static com.android.server.am.ActivityManagerService.ALLOW_NON_FULL;
+import static com.android.server.am.ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE;
+import static com.android.server.am.ActivityManagerService.MY_PID;
 import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_COMPLETE_MSG;
 import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG;
 import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG;
@@ -31,8 +37,10 @@
 
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.Dialog;
 import android.app.IStopUserCallback;
 import android.app.IUserSwitchObserver;
+import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -40,11 +48,15 @@
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Debug;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.IRemoteCallback;
+import android.os.IUserManager;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
@@ -58,7 +70,9 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
@@ -76,9 +90,9 @@
     private final Handler mHandler;
 
     // Holds the current foreground user's id
-    int mCurrentUserId = 0;
+    private int mCurrentUserId = UserHandle.USER_SYSTEM;
     // Holds the target user's id during a user switch
-    int mTargetUserId = UserHandle.USER_NULL;
+    private int mTargetUserId = UserHandle.USER_NULL;
 
     /**
      * Which users have been started, so are allowed to run code.
@@ -96,7 +110,7 @@
 
     // If there are multiple profiles for the current user, their ids are here
     // Currently only the primary user can have managed profiles
-    int[] mCurrentProfileIds = new int[] {}; // Accessed by ActivityStack
+    private int[] mCurrentProfileIds = new int[] {};
 
     /**
      * Mapping from each known user ID to the profile group ID it is associated with.
@@ -106,7 +120,7 @@
     /**
      * Registered observers of the user switching mechanics.
      */
-    final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
+    private final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
             = new RemoteCallbackList<>();
 
     /**
@@ -114,6 +128,8 @@
      */
     Object mCurUserSwitchCallback;
 
+    private volatile UserManagerService mUserManager;
+
     UserController(ActivityManagerService service) {
         mService = service;
         mHandler = mService.mHandler;
@@ -176,8 +192,7 @@
                 mService.broadcastIntentLocked(null, null, intent,
                         null, null, 0, null, null,
                         new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
-                        AppOpsManager.OP_NONE, null, true, false, ActivityManagerService.MY_PID,
-                        Process.SYSTEM_UID, userId);
+                        AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
             }
         }
     }
@@ -272,16 +287,14 @@
                         mService.mSystemServiceManager.stopUser(userId);
                         mService.broadcastIntentLocked(null, null, shutdownIntent,
                                 null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
-                                null, true, false, ActivityManagerService.MY_PID,
-                                android.os.Process.SYSTEM_UID, userId);
+                                null, true, false, MY_PID, SYSTEM_UID, userId);
                     }
                 };
                 // Kick things off.
                 mService.broadcastIntentLocked(null, null, stoppingIntent,
                         null, stoppingReceiver, 0, null, null,
                         new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
-                        null, true, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
-                        UserHandle.USER_ALL);
+                        null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -338,8 +351,7 @@
         intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
         mService.broadcastIntentLocked(null, null, intent,
                 null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                null, false, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
-                UserHandle.USER_ALL);
+                null, false, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
     }
 
 
@@ -357,7 +369,7 @@
                         || oldUss.mState == UserState.STATE_SHUTDOWN) {
                     continue;
                 }
-                UserInfo userInfo = getUserManagerLocked().getUserInfo(oldUserId);
+                UserInfo userInfo = getUserInfo(oldUserId);
                 if (userInfo.isGuest()) {
                     // This is a user to be stopped.
                     stopUserLocked(oldUserId, null);
@@ -369,7 +381,7 @@
 
     void startProfilesLocked() {
         if (DEBUG_MU) Slog.i(TAG, "startProfilesLocked");
-        List<UserInfo> profiles = getUserManagerLocked().getProfiles(
+        List<UserInfo> profiles = getUserManager().getProfiles(
                 mCurrentUserId, false /* enabledOnly */);
         List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
         for (UserInfo user : profiles) {
@@ -388,8 +400,13 @@
         }
     }
 
-    private UserManagerService getUserManagerLocked() {
-        return mService.getUserManagerLocked();
+    private UserManagerService getUserManager() {
+        UserManagerService userManager = mUserManager;
+        if (userManager == null) {
+            IBinder b = ServiceManager.getService(Context.USER_SERVICE);
+            userManager = mUserManager = (UserManagerService) IUserManager.Stub.asInterface(b);
+        }
+        return userManager;
     }
 
     boolean startUser(final int userId, final boolean foreground) {
@@ -416,7 +433,7 @@
                 mService.mStackSupervisor.setLockTaskModeLocked(null,
                         ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false);
 
-                final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
+                final UserInfo userInfo = getUserInfo(userId);
                 if (userInfo == null) {
                     Slog.w(TAG, "No user info for user #" + userId);
                     return false;
@@ -507,8 +524,7 @@
                     intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
                     mService.broadcastIntentLocked(null, null, intent,
                             null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                            null, false, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
-                            userId);
+                            null, false, false, MY_PID, SYSTEM_UID, userId);
                 }
 
                 if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
@@ -523,11 +539,10 @@
                                         onUserInitialized(uss, foreground, oldUserId, userId);
                                     }
                                 }, 0, null, null, null, AppOpsManager.OP_NONE,
-                                null, true, false, ActivityManagerService.MY_PID,
-                                Process.SYSTEM_UID, userId);
+                                null, true, false, MY_PID, SYSTEM_UID, userId);
                         uss.initializing = true;
                     } else {
-                        getUserManagerLocked().makeInitialized(userInfo.id);
+                        getUserManager().makeInitialized(userInfo.id);
                     }
                 }
 
@@ -551,9 +566,8 @@
                                         int sendingUser) throws RemoteException {
                                 }
                             }, 0, null, null,
-                            new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
-                            null, true, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
-                            UserHandle.USER_ALL);
+                            new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+                            null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
                 }
             }
         } finally {
@@ -563,6 +577,22 @@
         return true;
     }
 
+    /**
+     * Start user, if its not already running, and bring it to foreground.
+     */
+    boolean startUserInForeground(final int userId, Dialog dlg) {
+        boolean result = startUser(userId, /* foreground */ true);
+        dlg.dismiss();
+        return result;
+    }
+
+    void showUserSwitchDialog(int userId, String userName) {
+        // The dialog will show and then initiate the user switch by calling startUserInForeground
+        Dialog d = new UserSwitchingDialog(mService, mService.mContext, userId, userName,
+                true /* above system */);
+        d.show();
+    }
+
     void dispatchForegroundProfileChanged(int userId) {
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         for (int i = 0; i < observerCount; i++) {
@@ -657,7 +687,7 @@
         synchronized (mService) {
             if (clearInitializing) {
                 uss.initializing = false;
-                getUserManagerLocked().makeInitialized(uss.mHandle.getIdentifier());
+                getUserManager().makeInitialized(uss.mHandle.getIdentifier());
             }
             if (clearSwitching) {
                 uss.switching = false;
@@ -683,8 +713,143 @@
             mService.mStackSupervisor.resumeTopActivitiesLocked();
         }
         EventLogTags.writeAmSwitchUser(newUserId);
-        getUserManagerLocked().onUserForeground(newUserId);
-        mService.sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
+        getUserManager().onUserForeground(newUserId);
+        sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
+    }
+
+    void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            Intent intent;
+            if (oldUserId >= 0) {
+                // Send USER_BACKGROUND broadcast to all profiles of the outgoing user
+                List<UserInfo> profiles = getUserManager().getProfiles(oldUserId, false);
+                int count = profiles.size();
+                for (int i = 0; i < count; i++) {
+                    int profileUserId = profiles.get(i).id;
+                    intent = new Intent(Intent.ACTION_USER_BACKGROUND);
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                            | Intent.FLAG_RECEIVER_FOREGROUND);
+                    intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
+                    mService.broadcastIntentLocked(null, null, intent,
+                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                            null, false, false, MY_PID, SYSTEM_UID, profileUserId);
+                }
+            }
+            if (newUserId >= 0) {
+                // Send USER_FOREGROUND broadcast to all profiles of the incoming user
+                List<UserInfo> profiles = getUserManager().getProfiles(newUserId, false);
+                int count = profiles.size();
+                for (int i = 0; i < count; i++) {
+                    int profileUserId = profiles.get(i).id;
+                    intent = new Intent(Intent.ACTION_USER_FOREGROUND);
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                            | Intent.FLAG_RECEIVER_FOREGROUND);
+                    intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
+                    mService.broadcastIntentLocked(null, null, intent,
+                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                            null, false, false, MY_PID, SYSTEM_UID, profileUserId);
+                }
+                intent = new Intent(Intent.ACTION_USER_SWITCHED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                        | Intent.FLAG_RECEIVER_FOREGROUND);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
+                mService.broadcastIntentLocked(null, null, intent,
+                        null, null, 0, null, null,
+                        new String[] {android.Manifest.permission.MANAGE_USERS},
+                        AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+                        UserHandle.USER_ALL);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+
+    int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
+            int allowMode, String name, String callerPackage) {
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (callingUserId == userId) {
+            return userId;
+        }
+
+        // Note that we may be accessing mCurrentUserId outside of a lock...
+        // shouldn't be a big deal, if this is being called outside
+        // of a locked context there is intrinsically a race with
+        // the value the caller will receive and someone else changing it.
+        // We assume that USER_CURRENT_OR_SELF will use the current user; later
+        // we will switch to the calling user if access to the current user fails.
+        int targetUserId = unsafeConvertIncomingUserLocked(userId);
+
+        if (callingUid != 0 && callingUid != SYSTEM_UID) {
+            final boolean allow;
+            if (mService.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
+                    callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
+                // If the caller has this permission, they always pass go.  And collect $200.
+                allow = true;
+            } else if (allowMode == ALLOW_FULL_ONLY) {
+                // We require full access, sucks to be you.
+                allow = false;
+            } else if (mService.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
+                    callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
+                // If the caller does not have either permission, they are always doomed.
+                allow = false;
+            } else if (allowMode == ALLOW_NON_FULL) {
+                // We are blanket allowing non-full access, you lucky caller!
+                allow = true;
+            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
+                // We may or may not allow this depending on whether the two users are
+                // in the same profile.
+                allow = isSameProfileGroup(callingUserId, targetUserId);
+            } else {
+                throw new IllegalArgumentException("Unknown mode: " + allowMode);
+            }
+            if (!allow) {
+                if (userId == UserHandle.USER_CURRENT_OR_SELF) {
+                    // In this case, they would like to just execute as their
+                    // owner user instead of failing.
+                    targetUserId = callingUserId;
+                } else {
+                    StringBuilder builder = new StringBuilder(128);
+                    builder.append("Permission Denial: ");
+                    builder.append(name);
+                    if (callerPackage != null) {
+                        builder.append(" from ");
+                        builder.append(callerPackage);
+                    }
+                    builder.append(" asks to run as user ");
+                    builder.append(userId);
+                    builder.append(" but is calling from user ");
+                    builder.append(UserHandle.getUserId(callingUid));
+                    builder.append("; this requires ");
+                    builder.append(INTERACT_ACROSS_USERS_FULL);
+                    if (allowMode != ALLOW_FULL_ONLY) {
+                        builder.append(" or ");
+                        builder.append(INTERACT_ACROSS_USERS);
+                    }
+                    String msg = builder.toString();
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                }
+            }
+        }
+        if (!allowAll && targetUserId < 0) {
+            throw new IllegalArgumentException(
+                    "Call does not support special user #" + targetUserId);
+        }
+        // Check shell permission
+        if (callingUid == Process.SHELL_UID && targetUserId >= UserHandle.USER_SYSTEM) {
+            if (hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId)) {
+                throw new SecurityException("Shell does not have permission to access user "
+                        + targetUserId + "\n " + Debug.getCallers(3));
+            }
+        }
+        return targetUserId;
+    }
+
+    int unsafeConvertIncomingUserLocked(int userId) {
+        return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
+                ? getCurrentUserIdLocked(): userId;
     }
 
     void registerUserSwitchObserver(IUserSwitchObserver observer) {
@@ -705,7 +870,7 @@
         mUserSwitchObservers.unregister(observer);
     }
 
-    UserState getStartedUserState(int userId) {
+    UserState getStartedUserStateLocked(int userId) {
         return mStartedUsers.get(userId);
     }
 
@@ -746,9 +911,8 @@
                 intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
                 mService.broadcastIntentLocked(null, null, intent, null,
                         resultTo, 0, null, null,
-                        new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
-                        AppOpsManager.OP_NONE, null, true, false,
-                        ActivityManagerService.MY_PID, Process.SYSTEM_UID, userId);
+                        new String[] {android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+                        AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
             }
         }
     }
@@ -759,7 +923,7 @@
      * background.
      */
     void updateCurrentProfileIdsLocked() {
-        final List<UserInfo> profiles = getUserManagerLocked().getProfiles(mCurrentUserId,
+        final List<UserInfo> profiles = getUserManager().getProfiles(mCurrentUserId,
                 false /* enabledOnly */);
         int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
         for (int i = 0; i < currentProfileIds.length; i++) {
@@ -769,7 +933,7 @@
 
         synchronized (mUserProfileGroupIdsSelfLocked) {
             mUserProfileGroupIdsSelfLocked.clear();
-            final List<UserInfo> users = getUserManagerLocked().getUsers(false);
+            final List<UserInfo> users = getUserManager().getUsers(false);
             for (int i = 0; i < users.size(); i++) {
                 UserInfo user = users.get(i);
                 if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
@@ -783,6 +947,18 @@
         return mStartedUserArray;
     }
 
+    boolean isUserRunningLocked(int userId, boolean orStopped) {
+        UserState state = getStartedUserStateLocked(userId);
+        if (state == null) {
+            return false;
+        }
+        if (orStopped) {
+            return true;
+        }
+        return state.mState != UserState.STATE_STOPPING
+                && state.mState != UserState.STATE_SHUTDOWN;
+    }
+
     UserInfo getCurrentUser() {
         if ((mService.checkCallingPermission(INTERACT_ACROSS_USERS)
                 != PackageManager.PERMISSION_GRANTED) && (
@@ -802,11 +978,50 @@
 
     UserInfo getCurrentUserLocked() {
         int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
-        return getUserManagerLocked().getUserInfo(userId);
+        return getUserInfo(userId);
+    }
+
+    int getCurrentOrTargetUserIdLocked() {
+        return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
     }
 
     int getCurrentUserIdLocked() {
-        return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+        return mCurrentUserId;
+    }
+
+    int setTargetUserIdLocked(int targetUserId) {
+        return mTargetUserId = targetUserId;
+    }
+
+    int[] getUsers() {
+        UserManagerService ums = getUserManager();
+        return ums != null ? ums.getUserIds() : new int[] { 0 };
+    }
+
+    UserInfo getUserInfo(int userId) {
+        return getUserManager().getUserInfo(userId);
+    }
+
+    int[] getUserIds() {
+        return getUserManager().getUserIds();
+    }
+
+    boolean exists(int userId) {
+        return getUserManager().exists(userId);
+    }
+
+    boolean hasUserRestriction(String restriction, int userId) {
+        return getUserManager().hasUserRestriction(restriction, userId);
+    }
+
+    Set<Integer> getProfileIds(int userId) {
+        Set<Integer> userIds = new HashSet<>();
+        final List<UserInfo> profiles = getUserManager().getProfiles(userId,
+                false /* enabledOnly */);
+        for (UserInfo user : profiles) {
+            userIds.add(user.id);
+        }
+        return userIds;
     }
 
     boolean isSameProfileGroup(int callingUserId, int targetUserId) {
@@ -824,6 +1039,10 @@
         return ArrayUtils.contains(mCurrentProfileIds, userId);
     }
 
+    int[] getCurrentProfileIdsLocked() {
+        return mCurrentProfileIds;
+    }
+
     void dump(PrintWriter pw, boolean dumpAll) {
         pw.println("  mStartedUsers:");
         for (int i = 0; i < mStartedUsers.size(); i++) {
@@ -858,5 +1077,4 @@
             }
         }
     }
-
 }
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 5a66f4a..28b4096 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -97,7 +97,7 @@
     void startUser() {
         synchronized (this) {
             if (!mStartedUser) {
-                mService.startUserInForeground(mUserId, this);
+                mService.mUserController.startUserInForeground(mUserId, this);
                 mStartedUser = true;
                 final View decorView = getWindow().getDecorView();
                 if (decorView != null) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 66e731a..d89f47c 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1756,7 +1756,8 @@
         if (uid == android.os.Process.SYSTEM_UID) {
             uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
         }
-        if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
+        // If OP_AUDIO_MASTER_VOLUME is set, disallow unmuting.
+        if (!mute && mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
                 != AppOpsManager.MODE_ALLOWED) {
             return;
         }
@@ -1848,7 +1849,8 @@
         if (uid == android.os.Process.SYSTEM_UID) {
             uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
         }
-        if (mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)
+        // If OP_MUTE_MICROPHONE is set, disallow unmuting.
+        if (!on && mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)
                 != AppOpsManager.MODE_ALLOWED) {
             return;
         }
diff --git a/services/core/java/com/android/server/content/AppIdleMonitor.java b/services/core/java/com/android/server/content/AppIdleMonitor.java
index fe5c2da..2d768d8 100644
--- a/services/core/java/com/android/server/content/AppIdleMonitor.java
+++ b/services/core/java/com/android/server/content/AppIdleMonitor.java
@@ -50,8 +50,8 @@
         }
     }
 
-    boolean isAppIdle(String packageName, int userId) {
-        return !mAppIdleParoleOn && mUsageStats.isAppIdle(packageName, userId);
+    boolean isAppIdle(String packageName, int uidForAppId, int userId) {
+        return !mAppIdleParoleOn && mUsageStats.isAppIdle(packageName, uidForAppId, userId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 15e5393..82e0eaf 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -2625,9 +2625,18 @@
                         continue;
                     }
                     String packageName = getPackageName(op.target);
+                    ApplicationInfo ai = null;
+                    if (packageName != null) {
+                        try {
+                            ai = mContext.getPackageManager().getApplicationInfo(packageName,
+                                    PackageManager.GET_UNINSTALLED_PACKAGES
+                                    | PackageManager.GET_DISABLED_COMPONENTS);
+                        } catch (NameNotFoundException e) {
+                        }
+                    }
                     // If app is considered idle, then skip for now and backoff
-                    if (packageName != null
-                            && mAppIdleMonitor.isAppIdle(packageName, op.target.userId)) {
+                    if (ai != null
+                            && mAppIdleMonitor.isAppIdle(packageName, ai.uid, op.target.userId)) {
                         increaseBackoffSetting(op);
                         op.appIdle = true;
                         if (isLoggable) {
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index 02d4f40..6fc02f6 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -67,7 +67,7 @@
             mTrackedTasks.add(jobStatus);
             String packageName = jobStatus.job.getService().getPackageName();
             final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
-                    jobStatus.getUserId());
+                    jobStatus.uId, jobStatus.getUserId());
             if (DEBUG) {
                 Slog.d(LOG_TAG, "Start tracking, setting idle state of "
                         + packageName + " to " + appIdle);
@@ -108,7 +108,7 @@
             for (JobStatus task : mTrackedTasks) {
                 String packageName = task.job.getService().getPackageName();
                 final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
-                        task.getUserId());
+                        task.uId, task.getUserId());
                 if (DEBUG) {
                     Slog.d(LOG_TAG, "Setting idle state of " + packageName + " to " + appIdle);
                 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 17ae6dc..41aea04 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -2227,7 +2227,7 @@
         final int userId = UserHandle.getUserId(uid);
 
         for (String packageName : packages) {
-            if (!mUsageStats.isAppIdle(packageName, userId)) {
+            if (!mUsageStats.isAppIdle(packageName, uid, userId)) {
                 return false;
             }
         }
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index 0d6e8d6..1cdc6db 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -26,6 +26,7 @@
 import android.os.HandlerThread;
 import android.os.Message;
 import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.logging.MetricsLogger;
@@ -41,6 +42,7 @@
 import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Keeps track of notification activity, display, and user interaction.
@@ -367,26 +369,37 @@
                     break;
             }
 
-            for (String Key : n.extras.keySet()) {
-                if (Notification.EXTRA_BIG_TEXT.equals(key)) {
-                    numWithBigText++;
-                } else if (Notification.EXTRA_PICTURE.equals(key)) {
-                    numWithBigPicture++;
-                } else if (Notification.EXTRA_LARGE_ICON.equals(key)) {
-                    numWithLargeIcon++;
-                } else if (Notification.EXTRA_TEXT_LINES.equals(key)) {
-                    numWithInbox++;
-                } else if (Notification.EXTRA_MEDIA_SESSION.equals(key)) {
-                    numWithMediaSession++;
-                } else if (Notification.EXTRA_TITLE.equals(key)) {
-                    numWithTitle++;
-                } else if (Notification.EXTRA_TEXT.equals(key)) {
-                    numWithText++;
-                } else if (Notification.EXTRA_SUB_TEXT.equals(key)) {
-                    numWithSubText++;
-                } else if (Notification.EXTRA_INFO_TEXT.equals(key)) {
-                    numWithInfoText++;
-                }
+            final Set<String> names = n.extras.keySet();
+            if (names.contains(Notification.EXTRA_BIG_TEXT)) {
+                numWithBigText++;
+            }
+            if (names.contains(Notification.EXTRA_PICTURE)) {
+                numWithBigPicture++;
+            }
+            if (names.contains(Notification.EXTRA_LARGE_ICON)) {
+                numWithLargeIcon++;
+            }
+            if (names.contains(Notification.EXTRA_TEXT_LINES)) {
+                numWithInbox++;
+            }
+            if (names.contains(Notification.EXTRA_MEDIA_SESSION)) {
+                numWithMediaSession++;
+            }
+            if (names.contains(Notification.EXTRA_TITLE) &&
+                    !TextUtils.isEmpty(n.extras.getCharSequence(Notification.EXTRA_TITLE))) {
+                numWithTitle++;
+            }
+            if (names.contains(Notification.EXTRA_TEXT) &&
+                    !TextUtils.isEmpty(n.extras.getCharSequence(Notification.EXTRA_TEXT))) {
+                numWithText++;
+            }
+            if (names.contains(Notification.EXTRA_SUB_TEXT) &&
+                    !TextUtils.isEmpty(n.extras.getCharSequence(Notification.EXTRA_SUB_TEXT))) {
+                numWithSubText++;
+            }
+            if (names.contains(Notification.EXTRA_INFO_TEXT) &&
+                    !TextUtils.isEmpty(n.extras.getCharSequence(Notification.EXTRA_INFO_TEXT))) {
+                numWithInfoText++;
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index d867616..150c849 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -53,6 +53,14 @@
         mInstaller = new InstallerConnection();
     }
 
+    /**
+     * Yell loudly if someone tries making future calls while holding a lock on
+     * the given object.
+     */
+    public void setWarnIfHeld(Object warnIfHeld) {
+        mInstaller.setWarnIfHeld(warnIfHeld);
+    }
+
     @Override
     public void onStart() {
         Slog.i(TAG, "Waiting for installd to be ready.");
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 22bedc3..08f9bd5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -162,6 +162,7 @@
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.SELinux;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -2390,6 +2391,11 @@
         // tidy.
         Runtime.getRuntime().gc();
 
+        // The initial scanning above does many calls into installd while
+        // holding the mPackages lock, but we're mostly interested in yelling
+        // once we have a booted system.
+        mInstaller.setWarnIfHeld(mPackages);
+
         // Expose private service for system components to use.
         LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
     }
@@ -15086,6 +15092,13 @@
     }
 
     @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out,
+            FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+        (new PackageManagerShellCommand(this)).exec(
+                this, in, out, err, args, resultReceiver);
+    }
+
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -16545,7 +16558,7 @@
      */
     private void removeUnusedPackagesLILPw(UserManagerService userManager, final int userHandle) {
         final boolean DEBUG_CLEAN_APKS = false;
-        int [] users = userManager.getUserIdsLPr();
+        int [] users = userManager.getUserIds();
         Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
         while (psit.hasNext()) {
             PackageSetting ps = psit.next();
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
new file mode 100644
index 0000000..c259ac2
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -0,0 +1,529 @@
+package com.android.server.pm;
+
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.WeakHashMap;
+
+class PackageManagerShellCommand extends ShellCommand {
+    final IPackageManager mInterface;
+    final private WeakHashMap<String, Resources> mResourceCache =
+            new WeakHashMap<String, Resources>();
+
+    PackageManagerShellCommand(PackageManagerService service) {
+        mInterface = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+
+        final PrintWriter pw = getOutPrintWriter();
+        try {
+            switch(cmd) {
+                case "list":
+                    return runList();
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (RemoteException e) {
+            pw.println("Remote exception: " + e);
+        }
+        return -1;
+    }
+
+    private int runList() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final String type = getNextArg();
+        if (type == null) {
+            pw.println("Error: didn't specify type of data to list");
+            return -1;
+        }
+        switch(type) {
+            case "features":
+                return runListFeatures();
+            case "instrumentation":
+                return runListInstrumentation();
+            case "libraries":
+                return runListLibraries();
+            case "package":
+            case "packages":
+                return runListPackages(false /*showSourceDir*/);
+            case "permission-groups":
+                return runListPermissionGroups();
+            case "permissions":
+                return runListPermissions();
+        }
+        pw.println("Error: unknown list type '" + type + "'");
+        return -1;
+    }
+
+    private int runListFeatures() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final List<FeatureInfo> list = new ArrayList<FeatureInfo>();
+        final FeatureInfo[] rawList = mInterface.getSystemAvailableFeatures();
+        for (int i=0; i<rawList.length; i++) {
+            list.add(rawList[i]);
+        }
+
+        // sort by name
+        Collections.sort(list, new Comparator<FeatureInfo>() {
+            public int compare(FeatureInfo o1, FeatureInfo o2) {
+                if (o1.name == o2.name) return 0;
+                if (o1.name == null) return -1;
+                if (o2.name == null) return 1;
+                return o1.name.compareTo(o2.name);
+            }
+        });
+
+        final int count = (list != null) ? list.size() : 0;
+        for (int p = 0; p < count; p++) {
+            FeatureInfo fi = list.get(p);
+            pw.print("feature:");
+            if (fi.name != null) pw.println(fi.name);
+            else pw.println("reqGlEsVersion=0x"
+                    + Integer.toHexString(fi.reqGlEsVersion));
+        }
+        return 0;
+    }
+
+    private int runListInstrumentation() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        boolean showSourceDir = false;
+        String targetPackage = null;
+
+        try {
+            String opt;
+            while ((opt = getNextArg()) != null) {
+                switch (opt) {
+                    case "-f":
+                        showSourceDir = true;
+                        break;
+                    default:
+                        if (opt.charAt(0) != '-') {
+                            targetPackage = opt;
+                        } else {
+                            pw.println("Error: Unknown option: " + opt);
+                            return -1;
+                        }
+                        break;
+                }
+            }
+        } catch (RuntimeException ex) {
+            pw.println("Error: " + ex.toString());
+            return -1;
+        }
+
+        final List<InstrumentationInfo> list =
+                mInterface.queryInstrumentation(targetPackage, 0 /*flags*/);
+
+        // sort by target package
+        Collections.sort(list, new Comparator<InstrumentationInfo>() {
+            public int compare(InstrumentationInfo o1, InstrumentationInfo o2) {
+                return o1.targetPackage.compareTo(o2.targetPackage);
+            }
+        });
+
+        final int count = (list != null) ? list.size() : 0;
+        for (int p = 0; p < count; p++) {
+            final InstrumentationInfo ii = list.get(p);
+            pw.print("instrumentation:");
+            if (showSourceDir) {
+                pw.print(ii.sourceDir);
+                pw.print("=");
+            }
+            final ComponentName cn = new ComponentName(ii.packageName, ii.name);
+            pw.print(cn.flattenToShortString());
+            pw.print(" (target=");
+            pw.print(ii.targetPackage);
+            pw.println(")");
+        }
+        return 0;
+    }
+
+    private int runListLibraries() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final List<String> list = new ArrayList<String>();
+        final String[] rawList = mInterface.getSystemSharedLibraryNames();
+        for (int i = 0; i < rawList.length; i++) {
+            list.add(rawList[i]);
+        }
+
+        // sort by name
+        Collections.sort(list, new Comparator<String>() {
+            public int compare(String o1, String o2) {
+                if (o1 == o2) return 0;
+                if (o1 == null) return -1;
+                if (o2 == null) return 1;
+                return o1.compareTo(o2);
+            }
+        });
+
+        final int count = (list != null) ? list.size() : 0;
+        for (int p = 0; p < count; p++) {
+            String lib = list.get(p);
+            pw.print("library:");
+            pw.println(lib);
+        }
+        return 0;
+    }
+
+    private int runListPackages(boolean showSourceDir) throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        int getFlags = 0;
+        boolean listDisabled = false, listEnabled = false;
+        boolean listSystem = false, listThirdParty = false;
+        boolean listInstaller = false;
+        int userId = UserHandle.USER_SYSTEM;
+        try {
+            String opt;
+            while ((opt = getNextOption()) != null) {
+                switch (opt) {
+                    case "-d":
+                        listDisabled = true;
+                        break;
+                    case "-e":
+                        listEnabled = true;
+                        break;
+                    case "-f":
+                        showSourceDir = true;
+                        break;
+                    case "-i":
+                        listInstaller = true;
+                        break;
+                    case "-l":
+                        // old compat
+                        break;
+                    case "-lf":
+                        showSourceDir = true;
+                        break;
+                    case "-s":
+                        listSystem = true;
+                        break;
+                    case "-u":
+                        getFlags |= PackageManager.GET_UNINSTALLED_PACKAGES;
+                        break;
+                    case "-3":
+                        listThirdParty = true;
+                        break;
+                    case "--user":
+                        userId = Integer.parseInt(getNextArg());
+                        break;
+                    default:
+                        pw.println("Error: Unknown option: " + opt);
+                        return -1;
+                }
+            }
+        } catch (RuntimeException ex) {
+            pw.println("Error: " + ex.toString());
+            return -1;
+        }
+
+        final String filter = getNextArg();
+
+        @SuppressWarnings("unchecked")
+        final ParceledListSlice<PackageInfo> slice =
+                mInterface.getInstalledPackages(getFlags, userId);
+        final List<PackageInfo> packages = slice.getList();
+
+        final int count = packages.size();
+        for (int p = 0; p < count; p++) {
+            final PackageInfo info = packages.get(p);
+            if (filter != null && !info.packageName.contains(filter)) {
+                continue;
+            }
+            final boolean isSystem =
+                    (info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0;
+            if ((!listDisabled || !info.applicationInfo.enabled) &&
+                    (!listEnabled || info.applicationInfo.enabled) &&
+                    (!listSystem || isSystem) &&
+                    (!listThirdParty || !isSystem)) {
+                pw.print("package:");
+                if (showSourceDir) {
+                    pw.print(info.applicationInfo.sourceDir);
+                    pw.print("=");
+                }
+                pw.print(info.packageName);
+                if (listInstaller) {
+                    pw.print("  installer=");
+                    pw.print(mInterface.getInstallerPackageName(info.packageName));
+                }
+                pw.println();
+            }
+        }
+        return 0;
+    }
+
+    private int runListPermissionGroups() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final List<PermissionGroupInfo> pgs = mInterface.getAllPermissionGroups(0);
+
+        final int count = pgs.size();
+        for (int p = 0; p < count ; p++) {
+            final PermissionGroupInfo pgi = pgs.get(p);
+            pw.print("permission group:");
+            pw.println(pgi.name);
+        }
+        return 0;
+    }
+
+    private int runListPermissions() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        boolean labels = false;
+        boolean groups = false;
+        boolean userOnly = false;
+        boolean summary = false;
+        boolean dangerousOnly = false;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-d":
+                    dangerousOnly = true;
+                    break;
+                case "-f":
+                    labels = true;
+                    break;
+                case "-g":
+                    groups = true;
+                    break;
+                case "-s":
+                    groups = true;
+                    labels = true;
+                    summary = true;
+                    break;
+                case "-u":
+                    userOnly = true;
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        final ArrayList<String> groupList = new ArrayList<String>();
+        if (groups) {
+            final List<PermissionGroupInfo> infos =
+                    mInterface.getAllPermissionGroups(0 /*flags*/);
+            final int count = infos.size();
+            for (int i = 0; i < count; i++) {
+                groupList.add(infos.get(i).name);
+            }
+            groupList.add(null);
+        } else {
+            final String grp = getNextArg();
+            groupList.add(grp);
+        }
+
+        if (dangerousOnly) {
+            pw.println("Dangerous Permissions:");
+            pw.println("");
+            doListPermissions(groupList, groups, labels, summary,
+                    PermissionInfo.PROTECTION_DANGEROUS,
+                    PermissionInfo.PROTECTION_DANGEROUS);
+            if (userOnly) {
+                pw.println("Normal Permissions:");
+                pw.println("");
+                doListPermissions(groupList, groups, labels, summary,
+                        PermissionInfo.PROTECTION_NORMAL,
+                        PermissionInfo.PROTECTION_NORMAL);
+            }
+        } else if (userOnly) {
+            pw.println("Dangerous and Normal Permissions:");
+            pw.println("");
+            doListPermissions(groupList, groups, labels, summary,
+                    PermissionInfo.PROTECTION_NORMAL,
+                    PermissionInfo.PROTECTION_DANGEROUS);
+        } else {
+            pw.println("All Permissions:");
+            pw.println("");
+            doListPermissions(groupList, groups, labels, summary,
+                    -10000, 10000);
+        }
+        return 0;
+    }
+
+    private void doListPermissions(ArrayList<String> groupList, boolean groups, boolean labels,
+            boolean summary, int startProtectionLevel, int endProtectionLevel)
+                    throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final int groupCount = groupList.size();
+        for (int i = 0; i < groupCount; i++) {
+            String groupName = groupList.get(i);
+            String prefix = "";
+            if (groups) {
+                if (i > 0) {
+                    pw.println("");
+                }
+                if (groupName != null) {
+                    PermissionGroupInfo pgi =
+                            mInterface.getPermissionGroupInfo(groupName, 0 /*flags*/);
+                    if (summary) {
+                        Resources res = getResources(pgi);
+                        if (res != null) {
+                            pw.print(loadText(pgi, pgi.labelRes, pgi.nonLocalizedLabel) + ": ");
+                        } else {
+                            pw.print(pgi.name + ": ");
+
+                        }
+                    } else {
+                        pw.println((labels ? "+ " : "") + "group:" + pgi.name);
+                        if (labels) {
+                            pw.println("  package:" + pgi.packageName);
+                            Resources res = getResources(pgi);
+                            if (res != null) {
+                                pw.println("  label:"
+                                        + loadText(pgi, pgi.labelRes, pgi.nonLocalizedLabel));
+                                pw.println("  description:"
+                                        + loadText(pgi, pgi.descriptionRes,
+                                                pgi.nonLocalizedDescription));
+                            }
+                        }
+                    }
+                } else {
+                    pw.println(((labels && !summary) ? "+ " : "") + "ungrouped:");
+                }
+                prefix = "  ";
+            }
+            List<PermissionInfo> ps =
+                    mInterface.queryPermissionsByGroup(groupList.get(i), 0 /*flags*/);
+            final int count = ps.size();
+            boolean first = true;
+            for (int p = 0 ; p < count ; p++) {
+                PermissionInfo pi = ps.get(p);
+                if (groups && groupName == null && pi.group != null) {
+                    continue;
+                }
+                final int base = pi.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
+                if (base < startProtectionLevel
+                        || base > endProtectionLevel) {
+                    continue;
+                }
+                if (summary) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        pw.print(", ");
+                    }
+                    Resources res = getResources(pi);
+                    if (res != null) {
+                        pw.print(loadText(pi, pi.labelRes,
+                                pi.nonLocalizedLabel));
+                    } else {
+                        pw.print(pi.name);
+                    }
+                } else {
+                    pw.println(prefix + (labels ? "+ " : "")
+                            + "permission:" + pi.name);
+                    if (labels) {
+                        pw.println(prefix + "  package:" + pi.packageName);
+                        Resources res = getResources(pi);
+                        if (res != null) {
+                            pw.println(prefix + "  label:"
+                                    + loadText(pi, pi.labelRes,
+                                            pi.nonLocalizedLabel));
+                            pw.println(prefix + "  description:"
+                                    + loadText(pi, pi.descriptionRes,
+                                            pi.nonLocalizedDescription));
+                        }
+                        pw.println(prefix + "  protectionLevel:"
+                                + PermissionInfo.protectionToString(pi.protectionLevel));
+                    }
+                }
+            }
+
+            if (summary) {
+                pw.println("");
+            }
+        }
+    }
+
+    private String loadText(PackageItemInfo pii, int res, CharSequence nonLocalized)
+            throws RemoteException {
+        if (nonLocalized != null) {
+            return nonLocalized.toString();
+        }
+        if (res != 0) {
+            Resources r = getResources(pii);
+            if (r != null) {
+                try {
+                    return r.getString(res);
+                } catch (Resources.NotFoundException e) {
+                }
+            }
+        }
+        return null;
+    }
+
+    private Resources getResources(PackageItemInfo pii) throws RemoteException {
+        Resources res = mResourceCache.get(pii.packageName);
+        if (res != null) return res;
+
+        ApplicationInfo ai = mInterface.getApplicationInfo(pii.packageName, 0, 0);
+        AssetManager am = new AssetManager();
+        am.addAssetPath(ai.publicSourceDir);
+        res = new Resources(am, null, null);
+        mResourceCache.put(pii.packageName, res);
+        return res;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+        pw.println("Package manager (package) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("");
+        pw.println("  list features");
+        pw.println("    Prints all features of the system.");
+        pw.println("  list instrumentation [-f] [TARGET-PACKAGE]");
+        pw.println("    Prints all test packages; optionally only those targetting TARGET-PACKAGE");
+        pw.println("    Options:");
+        pw.println("      -f: dump the name of the .apk file containing the test package");
+        pw.println("  list libraries");
+        pw.println("    Prints all system libraries.");
+        pw.println("  list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER]");
+        pw.println("    Prints all packages; optionally only those whose name contains");
+        pw.println("    the text in FILTER.");
+        pw.println("    Options:");
+        pw.println("      -f: see their associated file");
+        pw.println("      -d: filter to only show disbled packages");
+        pw.println("      -e: filter to only show enabled packages");
+        pw.println("      -s: filter to only show system packages");
+        pw.println("      -3: filter to only show third party packages");
+        pw.println("      -i: see the installer for the packages");
+        pw.println("      -u: also include uninstalled packages");
+        pw.println("  list permission-groups");
+        pw.println("    Prints all known permission groups.");
+        pw.println("  list permissions [-g] [-f] [-d] [-u] [GROUP]");
+        pw.println("    Prints all known permissions; optionally only those in GROUP.");
+        pw.println("    Options:");
+        pw.println("      -g: organize by group");
+        pw.println("      -f: print all information");
+        pw.println("      -s: short summary");
+        pw.println("      -d: only list dangerous permissions");
+        pw.println("      -u: list only the permissions users will see");
+        pw.println("");
+    }
+}
+
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index bbdfe31..78328f5 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -239,6 +239,7 @@
         keySetData = base.keySetData;
         verificationInfo = base.verificationInfo;
         installerPackageName = base.installerPackageName;
+        volumeUuid = base.volumeUuid;
     }
 
     private PackageUserState modifyUserState(int userId) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4dd7388..c41d493 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -22,6 +22,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
 import android.app.IStopUserCallback;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
@@ -44,7 +45,10 @@
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCommand;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
@@ -91,13 +95,15 @@
  * Service for {@link UserManager}.
  *
  * Method naming convention:
- * - Methods suffixed with "Locked" should be called within the {@code this} lock.
- * - Methods suffixed with "RL" should be called within the {@link #mRestrictionsLock} lock.
+ * <ul>
+ * <li> Methods suffixed with "LILP" should be called within {@link #mInstallLock} and
+ * {@link #mPackagesLock} locks obtained in the respective order.
+ * <li> Methods suffixed with "LR" should be called within the {@link #mRestrictionsLock} lock.
+ * <li> Methods suffixed with "LU" should be called within the {@link #mUsersLock} lock.
+ * </ul>
  */
 public class UserManagerService extends IUserManager.Stub {
-
     private static final String LOG_TAG = "UserManagerService";
-
     private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
 
     private static final String TAG_NAME = "name";
@@ -150,11 +156,6 @@
     // without first making sure that the rest of the framework is prepared for it.
     private static final int MAX_MANAGED_PROFILES = 1;
 
-    /**
-     * Flag indicating whether device credentials are shared among same-user profiles.
-     */
-    private static final boolean CONFIG_PROFILES_SHARE_CREDENTIAL = true;
-
     static final int WRITE_USER_MSG = 1;
     static final int WRITE_USER_DELAY = 2*1000;  // 2 seconds
 
@@ -164,15 +165,17 @@
     private final PackageManagerService mPm;
     private final Object mInstallLock;
     private final Object mPackagesLock;
+    // Short-term lock for internal state, when interaction/sync with PM is not required
+    private final Object mUsersLock = new Object();
+    private final Object mRestrictionsLock = new Object();
 
     private final Handler mHandler;
 
     private final File mUsersDir;
     private final File mUserListFile;
 
-    private final SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
-
-    private final Object mRestrictionsLock = new Object();
+    @GuardedBy("mUsersLock")
+    private final SparseArray<UserInfo> mUsers = new SparseArray<>();
 
     /**
      * User restrictions set via UserManager.  This doesn't include restrictions set by
@@ -183,7 +186,7 @@
      * maybe shared between {@link #mBaseUserRestrictions} and
      * {@link #mCachedEffectiveUserRestrictions}, but they should always updated separately.
      * (Otherwise we won't be able to detect what restrictions have changed in
-     * {@link #updateUserRestrictionsInternalRL).
+     * {@link #updateUserRestrictionsInternalLR}.
      */
     @GuardedBy("mRestrictionsLock")
     private final SparseArray<Bundle> mBaseUserRestrictions = new SparseArray<>();
@@ -198,20 +201,29 @@
      * maybe shared between {@link #mBaseUserRestrictions} and
      * {@link #mCachedEffectiveUserRestrictions}, but they should always updated separately.
      * (Otherwise we won't be able to detect what restrictions have changed in
-     * {@link #updateUserRestrictionsInternalRL).
+     * {@link #updateUserRestrictionsInternalLR}.
      */
     @GuardedBy("mRestrictionsLock")
     private final SparseArray<Bundle> mCachedEffectiveUserRestrictions = new SparseArray<>();
 
+    /**
+     * User restrictions that have already been applied in {@link #applyUserRestrictionsLR}.  We
+     * use it to detect restrictions that have changed since the last
+     * {@link #applyUserRestrictionsLR} call.
+     */
+    @GuardedBy("mRestrictionsLock")
+    private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
+
     private final Bundle mGuestRestrictions = new Bundle();
 
     /**
      * Set of user IDs being actively removed. Removed IDs linger in this set
      * for several seconds to work around a VFS caching issue.
      */
-    // @GuardedBy("mPackagesLock")
+    @GuardedBy("mUsersLock")
     private final SparseBooleanArray mRemovingUserIds = new SparseBooleanArray();
 
+    @GuardedBy("mUsersLock")
     private int[] mUserIds;
     private int mNextSerialNumber;
     private int mUserVersion = 0;
@@ -271,7 +283,7 @@
                         -1, -1);
                 mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
                 initDefaultGuestRestrictions();
-                readUserListLocked();
+                readUserListLILP();
                 sInstance = this;
             }
         }
@@ -282,21 +294,23 @@
     void systemReady() {
         synchronized (mInstallLock) {
             synchronized (mPackagesLock) {
-                // Prune out any partially created/partially removed users.
-                ArrayList<UserInfo> partials = new ArrayList<UserInfo>();
-                final int userSize = mUsers.size();
-                for (int i = 0; i < userSize; i++) {
-                    UserInfo ui = mUsers.valueAt(i);
-                    if ((ui.partial || ui.guestToRemove) && i != 0) {
-                        partials.add(ui);
+                synchronized (mUsersLock) {
+                    // Prune out any partially created/partially removed users.
+                    ArrayList<UserInfo> partials = new ArrayList<UserInfo>();
+                    final int userSize = mUsers.size();
+                    for (int i = 0; i < userSize; i++) {
+                        UserInfo ui = mUsers.valueAt(i);
+                        if ((ui.partial || ui.guestToRemove) && i != 0) {
+                            partials.add(ui);
+                        }
                     }
-                }
-                final int partialsSize = partials.size();
-                for (int i = 0; i < partialsSize; i++) {
-                    UserInfo ui = partials.get(i);
-                    Slog.w(LOG_TAG, "Removing partially created user " + ui.id
-                            + " (name=" + ui.name + ")");
-                    removeUserStateLocked(ui.id);
+                    final int partialsSize = partials.size();
+                    for (int i = 0; i < partialsSize; i++) {
+                        UserInfo ui = partials.get(i);
+                        Slog.w(LOG_TAG, "Removing partially created user " + ui.id
+                                + " (name=" + ui.name + ")");
+                        removeUserStateLILP(ui.id);
+                    }
                 }
             }
         }
@@ -316,7 +330,7 @@
     @Override
     public UserInfo getPrimaryUser() {
         checkManageUsersPermission("query users");
-        synchronized (mPackagesLock) {
+        synchronized (mUsersLock) {
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
                 UserInfo ui = mUsers.valueAt(i);
@@ -331,7 +345,7 @@
     @Override
     public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
         checkManageUsersPermission("query users");
-        synchronized (mPackagesLock) {
+        synchronized (mUsersLock) {
             ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
@@ -354,8 +368,8 @@
         }
         final long ident = Binder.clearCallingIdentity();
         try {
-            synchronized (mPackagesLock) {
-                return getProfilesLocked(userId, enabledOnly);
+            synchronized (mUsersLock) {
+                return getProfilesLU(userId, enabledOnly);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -363,8 +377,8 @@
     }
 
     /** Assume permissions already checked and caller's identity cleared */
-    private List<UserInfo> getProfilesLocked(int userId, boolean enabledOnly) {
-        UserInfo user = getUserInfoLocked(userId);
+    private List<UserInfo> getProfilesLU(int userId, boolean enabledOnly) {
+        UserInfo user = getUserInfoLU(userId);
         ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
         if (user == null) {
             // Probably a dying user
@@ -390,9 +404,9 @@
     @Override
     public int getCredentialOwnerProfile(int userHandle) {
         checkManageUsersPermission("get the credential owner");
-        if (CONFIG_PROFILES_SHARE_CREDENTIAL) {
-            synchronized (mPackagesLock) {
-                UserInfo profileParent = getProfileParentLocked(userHandle);
+        if (!"file".equals(SystemProperties.get("ro.crypto.type", "none"))) {
+            synchronized (mUsersLock) {
+                UserInfo profileParent = getProfileParentLU(userHandle);
                 if (profileParent != null) {
                     return profileParent.id;
                 }
@@ -407,32 +421,35 @@
         if (userId == otherUserId) return true;
         checkManageUsersPermission("check if in the same profile group");
         synchronized (mPackagesLock) {
-            return isSameProfileGroupLocked(userId, otherUserId);
+            return isSameProfileGroupLP(userId, otherUserId);
         }
     }
 
-    private boolean isSameProfileGroupLocked(int userId, int otherUserId) {
-        UserInfo userInfo = getUserInfoLocked(userId);
-        if (userInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
-            return false;
+    private boolean isSameProfileGroupLP(int userId, int otherUserId) {
+        synchronized (mUsersLock) {
+            UserInfo userInfo = getUserInfoLU(userId);
+            if (userInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+                return false;
+            }
+            UserInfo otherUserInfo = getUserInfoLU(otherUserId);
+            if (otherUserInfo == null
+                    || otherUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+                return false;
+            }
+            return userInfo.profileGroupId == otherUserInfo.profileGroupId;
         }
-        UserInfo otherUserInfo = getUserInfoLocked(otherUserId);
-        if (otherUserInfo == null || otherUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
-            return false;
-        }
-        return userInfo.profileGroupId == otherUserInfo.profileGroupId;
     }
 
     @Override
     public UserInfo getProfileParent(int userHandle) {
         checkManageUsersPermission("get the profile parent");
-        synchronized (mPackagesLock) {
-            return getProfileParentLocked(userHandle);
+        synchronized (mUsersLock) {
+            return getProfileParentLU(userHandle);
         }
     }
 
-    private UserInfo getProfileParentLocked(int userHandle) {
-        UserInfo profile = getUserInfoLocked(userHandle);
+    private UserInfo getProfileParentLU(int userHandle) {
+        UserInfo profile = getUserInfoLU(userHandle);
         if (profile == null) {
             return null;
         }
@@ -440,11 +457,11 @@
         if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
             return null;
         } else {
-            return getUserInfoLocked(parentUserId);
+            return getUserInfoLU(parentUserId);
         }
     }
 
-    private boolean isProfileOf(UserInfo user, UserInfo profile) {
+    private static boolean isProfileOf(UserInfo user, UserInfo profile) {
         return user.id == profile.id ||
                 (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
                 && user.profileGroupId == profile.profileGroupId);
@@ -454,10 +471,13 @@
     public void setUserEnabled(int userId) {
         checkManageUsersPermission("enable user");
         synchronized (mPackagesLock) {
-            UserInfo info = getUserInfoLocked(userId);
+            UserInfo info;
+            synchronized (mUsersLock) {
+                info = getUserInfoLU(userId);
+            }
             if (info != null && !info.isEnabled()) {
                 info.flags ^= UserInfo.FLAG_DISABLED;
-                writeUserLocked(info);
+                writeUserLP(info);
             }
         }
     }
@@ -465,23 +485,23 @@
     @Override
     public UserInfo getUserInfo(int userId) {
         checkManageUsersPermission("query user");
-        synchronized (mPackagesLock) {
-            return getUserInfoLocked(userId);
+        synchronized (mUsersLock) {
+            return getUserInfoLU(userId);
         }
     }
 
     @Override
     public boolean isRestricted() {
-        synchronized (mPackagesLock) {
-            return getUserInfoLocked(UserHandle.getCallingUserId()).isRestricted();
+        synchronized (mUsersLock) {
+            return getUserInfoLU(UserHandle.getCallingUserId()).isRestricted();
         }
     }
 
     @Override
     public boolean canHaveRestrictedProfile(int userId) {
         checkManageUsersPermission("canHaveRestrictedProfile");
-        synchronized (mPackagesLock) {
-            final UserInfo userInfo = getUserInfoLocked(userId);
+        synchronized (mUsersLock) {
+            final UserInfo userInfo = getUserInfoLU(userId);
             if (userInfo == null || !userInfo.canHaveProfile()) {
                 return false;
             }
@@ -498,7 +518,7 @@
     /*
      * Should be locked on mUsers before calling this.
      */
-    private UserInfo getUserInfoLocked(int userId) {
+    private UserInfo getUserInfoLU(int userId) {
         UserInfo ui = mUsers.get(userId);
         // If it is partial and not in the process of being removed, return as unknown user.
         if (ui != null && ui.partial && !mRemovingUserIds.get(userId)) {
@@ -508,11 +528,19 @@
         return ui;
     }
 
+    /**
+     * Obtains {@link #mUsersLock} and return UserInfo from mUsers.
+     * <p>No permissions checking or any addition checks are made</p>
+     */
+    private UserInfo getUserInfoNoChecks(int userId) {
+        synchronized (mUsersLock) {
+            return mUsers.get(userId);
+        }
+    }
+
     /** Called by PackageManagerService */
     public boolean exists(int userId) {
-        synchronized (mPackagesLock) {
-            return mUsers.get(userId) != null;
-        }
+        return getUserInfoNoChecks(userId) != null;
     }
 
     @Override
@@ -520,14 +548,14 @@
         checkManageUsersPermission("rename users");
         boolean changed = false;
         synchronized (mPackagesLock) {
-            UserInfo info = mUsers.get(userId);
+            UserInfo info = getUserInfoNoChecks(userId);
             if (info == null || info.partial) {
                 Slog.w(LOG_TAG, "setUserName: unknown user #" + userId);
                 return;
             }
             if (name != null && !name.equals(info.name)) {
                 info.name = name;
-                writeUserLocked(info);
+                writeUserLP(info);
                 changed = true;
             }
         }
@@ -542,13 +570,13 @@
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mPackagesLock) {
-                UserInfo info = mUsers.get(userId);
+                UserInfo info = getUserInfoNoChecks(userId);
                 if (info == null || info.partial) {
                     Slog.w(LOG_TAG, "setUserIcon: unknown user #" + userId);
                     return;
                 }
-                writeBitmapLocked(info, bitmap);
-                writeUserLocked(info);
+                writeBitmapLP(info, bitmap);
+                writeUserLP(info);
             }
             sendUserInfoChangedBroadcast(userId);
         } finally {
@@ -567,12 +595,12 @@
     public ParcelFileDescriptor getUserIcon(int userId) {
         String iconPath;
         synchronized (mPackagesLock) {
-            UserInfo info = mUsers.get(userId);
+            UserInfo info = getUserInfoNoChecks(userId);
             if (info == null || info.partial) {
                 Slog.w(LOG_TAG, "getUserIcon: unknown user #" + userId);
                 return null;
             }
-            int callingGroupId = mUsers.get(UserHandle.getCallingUserId()).profileGroupId;
+            int callingGroupId = getUserInfoNoChecks(UserHandle.getCallingUserId()).profileGroupId;
             if (callingGroupId == UserInfo.NO_PROFILE_GROUP_ID
                     || callingGroupId != info.profileGroupId) {
                 checkManageUsersPermission("get the icon of a user who is not related");
@@ -595,13 +623,14 @@
     public void makeInitialized(int userId) {
         checkManageUsersPermission("makeInitialized");
         synchronized (mPackagesLock) {
-            UserInfo info = mUsers.get(userId);
+            UserInfo info = getUserInfoNoChecks(userId);
             if (info == null || info.partial) {
                 Slog.w(LOG_TAG, "makeInitialized: unknown user #" + userId);
+                // TODO Check if we should return here instead of a null check below
             }
-            if ((info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
+            if (info != null && (info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
                 info.flags |= UserInfo.FLAG_INITIALIZED;
-                scheduleWriteUserLocked(info);
+                scheduleWriteUserLP(info);
             }
         }
     }
@@ -620,6 +649,7 @@
     @Override
     public Bundle getDefaultGuestRestrictions() {
         checkManageUsersPermission("getDefaultGuestRestrictions");
+        // TODO Switch to mGuestRestrictions for locking
         synchronized (mPackagesLock) {
             return new Bundle(mGuestRestrictions);
         }
@@ -628,15 +658,17 @@
     @Override
     public void setDefaultGuestRestrictions(Bundle restrictions) {
         checkManageUsersPermission("setDefaultGuestRestrictions");
-        synchronized (mPackagesLock) {
-            mGuestRestrictions.clear();
-            mGuestRestrictions.putAll(restrictions);
-            writeUserListLocked();
+        synchronized (mInstallLock) {
+            synchronized (mPackagesLock) {
+                mGuestRestrictions.clear();
+                mGuestRestrictions.putAll(restrictions);
+                writeUserListLILP();
+            }
         }
     }
 
     @GuardedBy("mRestrictionsLock")
-    private Bundle computeEffectiveUserRestrictionsRL(int userId) {
+    private Bundle computeEffectiveUserRestrictionsLR(int userId) {
         final DevicePolicyManagerInternal dpmi =
                 LocalServices.getService(DevicePolicyManagerInternal.class);
         final Bundle systemRestrictions = mBaseUserRestrictions.get(userId);
@@ -652,7 +684,7 @@
     }
 
     @GuardedBy("mRestrictionsLock")
-    private void invalidateEffectiveUserRestrictionsRL(int userId) {
+    private void invalidateEffectiveUserRestrictionsLR(int userId) {
         if (DBG) {
             Log.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
         }
@@ -663,7 +695,7 @@
         synchronized (mRestrictionsLock) {
             Bundle restrictions = mCachedEffectiveUserRestrictions.get(userId);
             if (restrictions == null) {
-                restrictions = computeEffectiveUserRestrictionsRL(userId);
+                restrictions = computeEffectiveUserRestrictionsLR(userId);
                 mCachedEffectiveUserRestrictions.put(userId, restrictions);
             }
             return restrictions;
@@ -709,7 +741,7 @@
             UserRestrictionsUtils.merge(newRestrictions, mBaseUserRestrictions.get(userId));
             newRestrictions.putBoolean(key, value);
 
-            updateUserRestrictionsInternalRL(newRestrictions, userId);
+            updateUserRestrictionsInternalLR(newRestrictions, userId);
         }
     }
 
@@ -725,14 +757,12 @@
      * @param userId target user ID.
      */
     @GuardedBy("mRestrictionsLock")
-    private void updateUserRestrictionsInternalRL(
+    private void updateUserRestrictionsInternalLR(
             @Nullable Bundle newRestrictions, int userId) {
         if (DBG) {
             Log.d(LOG_TAG, "updateUserRestrictionsInternalLocked userId=" + userId
                     + " bundle=" + newRestrictions);
         }
-        final Bundle prevRestrictions = getEffectiveUserRestrictions(userId);
-
         // Update system restrictions.
         if (newRestrictions != null) {
             // If newRestrictions == the current one, it's probably a bug.
@@ -742,15 +772,22 @@
             mBaseUserRestrictions.put(userId, newRestrictions);
         }
 
-        mCachedEffectiveUserRestrictions.put(
-                userId, computeEffectiveUserRestrictionsRL(userId));
+        final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
 
-        applyUserRestrictionsRL(userId, mBaseUserRestrictions.get(userId), prevRestrictions);
+        mCachedEffectiveUserRestrictions.put(userId, effective);
+
+        applyUserRestrictionsLR(userId, effective);
     }
 
     @GuardedBy("mRestrictionsLock")
-    private void applyUserRestrictionsRL(int userId,
-            Bundle newRestrictions, Bundle prevRestrictions) {
+    private void applyUserRestrictionsLR(int userId, Bundle newRestrictions) {
+        final Bundle prevRestrictions = mAppliedUserRestrictions.get(userId);
+
+        if (DBG) {
+            Log.d(LOG_TAG, "applyUserRestrictionsRL userId=" + userId
+                    + " new=" + newRestrictions + " prev=" + prevRestrictions);
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             mAppOpsService.setUserRestrictions(newRestrictions, userId);
@@ -760,20 +797,22 @@
             Binder.restoreCallingIdentity(token);
         }
 
-        // TODO Move the code from DPMS.setUserRestriction().
+        UserRestrictionsUtils.applyUserRestrictions(
+                mContext, userId, newRestrictions, prevRestrictions);
+
+        mAppliedUserRestrictions.put(userId, new Bundle(newRestrictions));
     }
 
     @GuardedBy("mRestrictionsLock")
-    private void updateEffectiveUserRestrictionsRL(int userId) {
-        updateUserRestrictionsInternalRL(null, userId);
+    private void updateEffectiveUserRestrictionsLR(int userId) {
+        updateUserRestrictionsInternalLR(null, userId);
     }
 
     @GuardedBy("mRestrictionsLock")
-    private void updateEffectiveUserRestrictionsForAllUsersRL() {
+    private void updateEffectiveUserRestrictionsForAllUsersLR() {
         // First, invalidate all cached values.
-        synchronized (mRestrictionsLock) {
-            mCachedEffectiveUserRestrictions.clear();
-        }
+        mCachedEffectiveUserRestrictions.clear();
+
         // We don't want to call into ActivityManagerNative while taking a lock, so we'll call
         // it on a handler.
         final Runnable r = new Runnable() {
@@ -794,7 +833,7 @@
                 // TODO: "Apply restrictions upon user start hasn't been implemented.  Implement it.
                 synchronized (mRestrictionsLock) {
                     for (int i = 0; i < runningUsers.length; i++) {
-                        updateUserRestrictionsInternalRL(null, runningUsers[i]);
+                        updateUserRestrictionsInternalLR(null, runningUsers[i]);
                     }
                 }
             }
@@ -805,8 +844,12 @@
     /**
      * Check if we've hit the limit of how many users can be created.
      */
-    private boolean isUserLimitReachedLocked() {
-        return getAliveUsersExcludingGuestsCountLocked() >= UserManager.getMaxSupportedUsers();
+    private boolean isUserLimitReached() {
+        int count;
+        synchronized (mUsersLock) {
+            count = getAliveUsersExcludingGuestsCountLU();
+        }
+        return count >= UserManager.getMaxSupportedUsers();
     }
 
     @Override
@@ -824,18 +867,18 @@
         if (managedProfilesCount >= MAX_MANAGED_PROFILES) {
             return false;
         }
-        synchronized(mPackagesLock) {
-            UserInfo userInfo = getUserInfoLocked(userId);
+        synchronized(mUsersLock) {
+            UserInfo userInfo = getUserInfoLU(userId);
             if (!userInfo.canHaveProfile()) {
                 return false;
             }
-            int usersCount = getAliveUsersExcludingGuestsCountLocked();
+            int usersCount = getAliveUsersExcludingGuestsCountLU();
             // We allow creating a managed profile in the special case where there is only one user.
             return usersCount == 1 || usersCount < UserManager.getMaxSupportedUsers();
         }
     }
 
-    private int getAliveUsersExcludingGuestsCountLocked() {
+    private int getAliveUsersExcludingGuestsCountLU() {
         int aliveUserCount = 0;
         final int totalUserCount = mUsers.size();
         // Skip over users being removed
@@ -874,7 +917,7 @@
         }
     }
 
-    private void writeBitmapLocked(UserInfo info, Bitmap bitmap) {
+    private void writeBitmapLP(UserInfo info, Bitmap bitmap) {
         try {
             File dir = new File(mUsersDir, Integer.toString(info.id));
             File file = new File(dir, USER_PHOTO_FILENAME);
@@ -908,18 +951,14 @@
      * @return the array of user ids.
      */
     public int[] getUserIds() {
-        synchronized (mPackagesLock) {
+        synchronized (mUsersLock) {
             return mUserIds;
         }
     }
 
-    int[] getUserIdsLPr() {
-        return mUserIds;
-    }
-
-    private void readUserListLocked() {
+    private void readUserListLILP() {
         if (!mUserListFile.exists()) {
-            fallbackToSingleUserLocked();
+            fallbackToSingleUserLILP();
             return;
         }
         FileInputStream fis = null;
@@ -936,7 +975,7 @@
 
             if (type != XmlPullParser.START_TAG) {
                 Slog.e(LOG_TAG, "Unable to read user list");
-                fallbackToSingleUserLocked();
+                fallbackToSingleUserLILP();
                 return;
             }
 
@@ -957,12 +996,14 @@
                     final String name = parser.getName();
                     if (name.equals(TAG_USER)) {
                         String id = parser.getAttributeValue(null, ATTR_ID);
-                        UserInfo user = readUserLocked(Integer.parseInt(id));
+                        UserInfo user = readUserLILP(Integer.parseInt(id));
 
                         if (user != null) {
-                            mUsers.put(user.id, user);
-                            if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) {
-                                mNextSerialNumber = user.id + 1;
+                            synchronized (mUsersLock) {
+                                mUsers.put(user.id, user);
+                                if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) {
+                                    mNextSerialNumber = user.id + 1;
+                                }
                             }
                         }
                     } else if (name.equals(TAG_GUEST_RESTRICTIONS)) {
@@ -979,12 +1020,12 @@
                     }
                 }
             }
-            updateUserIdsLocked();
-            upgradeIfNecessaryLocked();
+            updateUserIds();
+            upgradeIfNecessaryLILP();
         } catch (IOException ioe) {
-            fallbackToSingleUserLocked();
+            fallbackToSingleUserLILP();
         } catch (XmlPullParserException pe) {
-            fallbackToSingleUserLocked();
+            fallbackToSingleUserLILP();
         } finally {
             if (fis != null) {
                 try {
@@ -998,24 +1039,24 @@
     /**
      * Upgrade steps between versions, either for fixing bugs or changing the data format.
      */
-    private void upgradeIfNecessaryLocked() {
+    private void upgradeIfNecessaryLILP() {
         int userVersion = mUserVersion;
         if (userVersion < 1) {
             // Assign a proper name for the owner, if not initialized correctly before
-            UserInfo user = mUsers.get(UserHandle.USER_SYSTEM);
+            UserInfo user = getUserInfoNoChecks(UserHandle.USER_SYSTEM);
             if ("Primary".equals(user.name)) {
                 user.name = mContext.getResources().getString(com.android.internal.R.string.owner_name);
-                scheduleWriteUserLocked(user);
+                scheduleWriteUserLP(user);
             }
             userVersion = 1;
         }
 
         if (userVersion < 2) {
             // Owner should be marked as initialized
-            UserInfo user = mUsers.get(UserHandle.USER_SYSTEM);
+            UserInfo user = getUserInfoNoChecks(UserHandle.USER_SYSTEM);
             if ((user.flags & UserInfo.FLAG_INITIALIZED) == 0) {
                 user.flags |= UserInfo.FLAG_INITIALIZED;
-                scheduleWriteUserLocked(user);
+                scheduleWriteUserLP(user);
             }
             userVersion = 2;
         }
@@ -1032,13 +1073,15 @@
 
         if (userVersion < 6) {
             final boolean splitSystemUser = UserManager.isSplitSystemUser();
-            for (int i = 0; i < mUsers.size(); i++) {
-                UserInfo user = mUsers.valueAt(i);
-                // In non-split mode, only user 0 can have restricted profiles
-                if (!splitSystemUser && user.isRestricted()
-                        && (user.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID)) {
-                    user.restrictedProfileParentId = UserHandle.USER_SYSTEM;
-                    scheduleWriteUserLocked(user);
+            synchronized (mUsersLock) {
+                for (int i = 0; i < mUsers.size(); i++) {
+                    UserInfo user = mUsers.valueAt(i);
+                    // In non-split mode, only user 0 can have restricted profiles
+                    if (!splitSystemUser && user.isRestricted()
+                            && (user.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID)) {
+                        user.restrictedProfileParentId = UserHandle.USER_SYSTEM;
+                        scheduleWriteUserLP(user);
+                    }
                 }
             }
             userVersion = 6;
@@ -1049,11 +1092,11 @@
                     + USER_VERSION);
         } else {
             mUserVersion = userVersion;
-            writeUserListLocked();
+            writeUserListLILP();
         }
     }
 
-    private void fallbackToSingleUserLocked() {
+    private void fallbackToSingleUserLILP() {
         int flags = UserInfo.FLAG_INITIALIZED;
         // In split system user mode, the admin and primary flags are assigned to the first human
         // user.
@@ -1064,7 +1107,9 @@
         UserInfo system = new UserInfo(UserHandle.USER_SYSTEM,
                 mContext.getResources().getString(com.android.internal.R.string.owner_name), null,
                 flags);
-        mUsers.put(system.id, system);
+        synchronized (mUsersLock) {
+            mUsers.put(system.id, system);
+        }
         mNextSerialNumber = MIN_USER_ID;
         mUserVersion = USER_VERSION;
 
@@ -1073,14 +1118,14 @@
             mBaseUserRestrictions.append(UserHandle.USER_SYSTEM, restrictions);
         }
 
-        updateUserIdsLocked();
+        updateUserIds();
         initDefaultGuestRestrictions();
 
-        writeUserListLocked();
-        writeUserLocked(system);
+        writeUserListLILP();
+        writeUserLP(system);
     }
 
-    private void scheduleWriteUserLocked(UserInfo userInfo) {
+    private void scheduleWriteUserLP(UserInfo userInfo) {
         if (!mHandler.hasMessages(WRITE_USER_MSG, userInfo)) {
             Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userInfo);
             mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
@@ -1094,7 +1139,7 @@
      *   <name>Primary</name>
      * </user>
      */
-    private void writeUserLocked(UserInfo userInfo) {
+    private void writeUserLP(UserInfo userInfo) {
         FileOutputStream fos = null;
         AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + XML_SUFFIX));
         try {
@@ -1160,7 +1205,8 @@
      *   <user id="2"></user>
      * </users>
      */
-    private void writeUserListLocked() {
+    private void writeUserListLILP() {
+        // TODO Investigate removing a dependency on mInstallLock
         FileOutputStream fos = null;
         AtomicFile userListFile = new AtomicFile(mUserListFile);
         try {
@@ -1181,11 +1227,17 @@
             UserRestrictionsUtils
                     .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
             serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
-            final int userSize = mUsers.size();
-            for (int i = 0; i < userSize; i++) {
-                UserInfo user = mUsers.valueAt(i);
+            int[] userIdsToWrite;
+            synchronized (mUsersLock) {
+                userIdsToWrite = new int[mUsers.size()];
+                for (int i = 0; i < userIdsToWrite.length; i++) {
+                    UserInfo user = mUsers.valueAt(i);
+                    userIdsToWrite[i] = user.id;
+                }
+            }
+            for (int id : userIdsToWrite) {
                 serializer.startTag(null, TAG_USER);
-                serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
+                serializer.attribute(null, ATTR_ID, Integer.toString(id));
                 serializer.endTag(null, TAG_USER);
             }
 
@@ -1199,7 +1251,7 @@
         }
     }
 
-    private UserInfo readUserLocked(int id) {
+    private UserInfo readUserLILP(int id) {
         int flags = 0;
         int serialNumber = id;
         String name = null;
@@ -1393,20 +1445,22 @@
                 synchronized (mPackagesLock) {
                     UserInfo parent = null;
                     if (parentId != UserHandle.USER_NULL) {
-                        parent = getUserInfoLocked(parentId);
+                        synchronized (mUsersLock) {
+                            parent = getUserInfoLU(parentId);
+                        }
                         if (parent == null) return null;
                     }
                     if (isManagedProfile && !canAddMoreManagedProfiles(parentId)) {
                         Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
                         return null;
                     }
-                    if (!isGuest && !isManagedProfile && isUserLimitReachedLocked()) {
+                    if (!isGuest && !isManagedProfile && isUserLimitReached()) {
                         // If we're not adding a guest user or a managed profile and the limit has
                         // been reached, cannot add a user.
                         return null;
                     }
                     // If we're adding a guest and there already exists one, bail.
-                    if (isGuest && findCurrentGuestUserLocked() != null) {
+                    if (isGuest && findCurrentGuestUser() != null) {
                         return null;
                     }
                     // In legacy mode, restricted profile's parent can only be the owner user
@@ -1440,7 +1494,7 @@
                             flags |= UserInfo.FLAG_ADMIN;
                         }
                     }
-                    userId = getNextAvailableIdLocked();
+                    userId = getNextAvailableId();
                     userInfo = new UserInfo(userId, name, null, flags);
                     userInfo.serialNumber = mNextSerialNumber++;
                     long now = System.currentTimeMillis();
@@ -1448,12 +1502,12 @@
                     userInfo.partial = true;
                     Environment.getUserSystemDirectory(userInfo.id).mkdirs();
                     mUsers.put(userId, userInfo);
-                    writeUserListLocked();
+                    writeUserListLILP();
                     if (parent != null) {
                         if (isManagedProfile) {
                             if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
                                 parent.profileGroupId = parent.id;
-                                scheduleWriteUserLocked(parent);
+                                scheduleWriteUserLP(parent);
                             }
                             userInfo.profileGroupId = parent.profileGroupId;
                         } else if (isRestricted) {
@@ -1462,7 +1516,7 @@
                             }
                             if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
                                 parent.restrictedProfileParentId = parent.id;
-                                scheduleWriteUserLocked(parent);
+                                scheduleWriteUserLP(parent);
                             }
                             userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
                         }
@@ -1481,8 +1535,8 @@
                     }
                     mPm.createNewUserLILPw(userId);
                     userInfo.partial = false;
-                    scheduleWriteUserLocked(userInfo);
-                    updateUserIdsLocked();
+                    scheduleWriteUserLP(userInfo);
+                    updateUserIds();
                     Bundle restrictions = new Bundle();
                     synchronized (mRestrictionsLock) {
                         mBaseUserRestrictions.append(userId, restrictions);
@@ -1525,12 +1579,14 @@
      * Find the current guest user. If the Guest user is partial,
      * then do not include it in the results as it is about to die.
      */
-    private UserInfo findCurrentGuestUserLocked() {
-        final int size = mUsers.size();
-        for (int i = 0; i < size; i++) {
-            final UserInfo user = mUsers.valueAt(i);
-            if (user.isGuest() && !user.guestToRemove && !mRemovingUserIds.get(user.id)) {
-                return user;
+    private UserInfo findCurrentGuestUser() {
+        synchronized (mUsersLock) {
+            final int size = mUsers.size();
+            for (int i = 0; i < size; i++) {
+                final UserInfo user = mUsers.valueAt(i);
+                if (user.isGuest() && !user.guestToRemove && !mRemovingUserIds.get(user.id)) {
+                    return user;
+                }
             }
         }
         return null;
@@ -1554,9 +1610,11 @@
         try {
             final UserInfo user;
             synchronized (mPackagesLock) {
-                user = mUsers.get(userHandle);
-                if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
-                    return false;
+                synchronized (mUsersLock) {
+                    user = mUsers.get(userHandle);
+                    if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
+                        return false;
+                    }
                 }
                 if (!user.isGuest()) {
                     return false;
@@ -1570,7 +1628,7 @@
                 // Mark it as disabled, so that it isn't returned any more when
                 // profiles are queried.
                 user.flags |= UserInfo.FLAG_DISABLED;
-                writeUserLocked(user);
+                writeUserLP(user);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -1600,15 +1658,17 @@
                 return false;
             }
             synchronized (mPackagesLock) {
-                user = mUsers.get(userHandle);
-                if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
-                    return false;
-                }
+                synchronized (mUsersLock) {
+                    user = mUsers.get(userHandle);
+                    if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
+                        return false;
+                    }
 
-                // We remember deleted user IDs to prevent them from being
-                // reused during the current boot; they can still be reused
-                // after a reboot.
-                mRemovingUserIds.put(userHandle, true);
+                    // We remember deleted user IDs to prevent them from being
+                    // reused during the current boot; they can still be reused
+                    // after a reboot.
+                    mRemovingUserIds.put(userHandle, true);
+                }
 
                 try {
                     mAppOpsService.removeUser(userHandle);
@@ -1622,7 +1682,7 @@
                 // Mark it as disabled, so that it isn't returned any more when
                 // profiles are queried.
                 user.flags |= UserInfo.FLAG_DISABLED;
-                writeUserLocked(user);
+                writeUserLP(user);
             }
 
             if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
@@ -1680,7 +1740,7 @@
                                             .onUserRemoved(userHandle);
                                     synchronized (mInstallLock) {
                                         synchronized (mPackagesLock) {
-                                            removeUserStateLocked(userHandle);
+                                            removeUserStateLILP(userHandle);
                                         }
                                     }
                                 }
@@ -1694,20 +1754,22 @@
         }
     }
 
-    private void removeUserStateLocked(final int userHandle) {
+    private void removeUserStateLILP(final int userHandle) {
         mContext.getSystemService(StorageManager.class)
             .deleteUserKey(userHandle);
         // Cleanup package manager settings
         mPm.cleanUpUserLILPw(this, userHandle);
 
         // Remove this user from the list
-        mUsers.remove(userHandle);
+        synchronized (mUsersLock) {
+            mUsers.remove(userHandle);
+        }
         // Remove user file
         AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
         userFile.delete();
         // Update the user list
-        writeUserListLocked();
-        updateUserIdsLocked();
+        writeUserListLILP();
+        updateUserIds();
         removeDirectoryRecursive(Environment.getUserSystemDirectory(userHandle));
     }
 
@@ -1743,7 +1805,7 @@
         }
         synchronized (mPackagesLock) {
             // Read the restrictions from XML
-            return readApplicationRestrictionsLocked(packageName, userId);
+            return readApplicationRestrictionsLP(packageName, userId);
         }
     }
 
@@ -1756,7 +1818,7 @@
                 cleanAppRestrictionsForPackage(packageName, userId);
             } else {
                 // Write the restrictions to XML
-                writeApplicationRestrictionsLocked(packageName, restrictions, userId);
+                writeApplicationRestrictionsLP(packageName, restrictions, userId);
             }
         }
 
@@ -1804,16 +1866,15 @@
         }
     }
 
-    private Bundle readApplicationRestrictionsLocked(String packageName,
-            int userId) {
+    private Bundle readApplicationRestrictionsLP(String packageName, int userId) {
         AtomicFile restrictionsFile =
                 new AtomicFile(new File(Environment.getUserSystemDirectory(userId),
                         packageToRestrictionsFileName(packageName)));
-        return readApplicationRestrictionsLocked(restrictionsFile);
+        return readApplicationRestrictionsLP(restrictionsFile);
     }
 
     @VisibleForTesting
-    static Bundle readApplicationRestrictionsLocked(AtomicFile restrictionsFile) {
+    static Bundle readApplicationRestrictionsLP(AtomicFile restrictionsFile) {
         final Bundle restrictions = new Bundle();
         final ArrayList<String> values = new ArrayList<>();
         if (!restrictionsFile.getBaseFile().exists()) {
@@ -1896,17 +1957,16 @@
         return childBundle;
     }
 
-    private void writeApplicationRestrictionsLocked(String packageName,
+    private void writeApplicationRestrictionsLP(String packageName,
             Bundle restrictions, int userId) {
         AtomicFile restrictionsFile = new AtomicFile(
                 new File(Environment.getUserSystemDirectory(userId),
                         packageToRestrictionsFileName(packageName)));
-        writeApplicationRestrictionsLocked(restrictions, restrictionsFile);
+        writeApplicationRestrictionsLP(restrictions, restrictionsFile);
     }
 
     @VisibleForTesting
-    static void writeApplicationRestrictionsLocked(Bundle restrictions,
-            AtomicFile restrictionsFile) {
+    static void writeApplicationRestrictionsLP(Bundle restrictions, AtomicFile restrictionsFile) {
         FileOutputStream fos = null;
         try {
             fos = restrictionsFile.startWrite();
@@ -1976,17 +2036,17 @@
 
     @Override
     public int getUserSerialNumber(int userHandle) {
-        synchronized (mPackagesLock) {
+        synchronized (mUsersLock) {
             if (!exists(userHandle)) return -1;
-            return getUserInfoLocked(userHandle).serialNumber;
+            return getUserInfoLU(userHandle).serialNumber;
         }
     }
 
     @Override
     public int getUserHandle(int userSerialNumber) {
-        synchronized (mPackagesLock) {
+        synchronized (mUsersLock) {
             for (int userId : mUserIds) {
-                UserInfo info = getUserInfoLocked(userId);
+                UserInfo info = getUserInfoLU(userId);
                 if (info != null && info.serialNumber == userSerialNumber) return userId;
             }
             // Not found
@@ -1998,13 +2058,13 @@
     public long getUserCreationTime(int userHandle) {
         int callingUserId = UserHandle.getCallingUserId();
         UserInfo userInfo = null;
-        synchronized (mPackagesLock) {
+        synchronized (mUsersLock) {
             if (callingUserId == userHandle) {
-                userInfo = getUserInfoLocked(userHandle);
+                userInfo = getUserInfoLU(userHandle);
             } else {
-                UserInfo parent = getProfileParentLocked(userHandle);
+                UserInfo parent = getProfileParentLU(userHandle);
                 if (parent != null && parent.id == callingUserId) {
-                    userInfo = getUserInfoLocked(userHandle);
+                    userInfo = getUserInfoLU(userHandle);
                 }
             }
         }
@@ -2018,22 +2078,24 @@
     /**
      * Caches the list of user ids in an array, adjusting the array size when necessary.
      */
-    private void updateUserIdsLocked() {
+    private void updateUserIds() {
         int num = 0;
-        final int userSize = mUsers.size();
-        for (int i = 0; i < userSize; i++) {
-            if (!mUsers.valueAt(i).partial) {
-                num++;
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
+                if (!mUsers.valueAt(i).partial) {
+                    num++;
+                }
             }
-        }
-        final int[] newUsers = new int[num];
-        int n = 0;
-        for (int i = 0; i < userSize; i++) {
-            if (!mUsers.valueAt(i).partial) {
-                newUsers[n++] = mUsers.keyAt(i);
+            final int[] newUsers = new int[num];
+            int n = 0;
+            for (int i = 0; i < userSize; i++) {
+                if (!mUsers.valueAt(i).partial) {
+                    newUsers[n++] = mUsers.keyAt(i);
+                }
             }
+            mUserIds = newUsers;
         }
-        mUserIds = newUsers;
     }
 
     /**
@@ -2042,7 +2104,7 @@
      */
     public void onUserForeground(int userId) {
         synchronized (mPackagesLock) {
-            UserInfo user = mUsers.get(userId);
+            UserInfo user = getUserInfoNoChecks(userId);
             long now = System.currentTimeMillis();
             if (user == null || user.partial) {
                 Slog.w(LOG_TAG, "userForeground: unknown user #" + userId);
@@ -2050,7 +2112,7 @@
             }
             if (now > EPOCH_PLUS_30_YEARS) {
                 user.lastLoggedInTime = now;
-                scheduleWriteUserLocked(user);
+                scheduleWriteUserLP(user);
             }
         }
     }
@@ -2061,8 +2123,8 @@
      * for data and battery stats collection, or unexpected cross-talk.
      * @return
      */
-    private int getNextAvailableIdLocked() {
-        synchronized (mPackagesLock) {
+    private int getNextAvailableId() {
+        synchronized (mUsersLock) {
             int i = MIN_USER_ID;
             while (i < MAX_USER_ID) {
                 if (mUsers.indexOfKey(i) < 0 && !mRemovingUserIds.get(i)) {
@@ -2154,6 +2216,45 @@
     }
 
     @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out,
+            FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+        (new Shell()).exec(this, in, out, err, args, resultReceiver);
+    }
+
+    int onShellCommand(Shell shell, String cmd) {
+        if (cmd == null) {
+            return shell.handleDefaultCommands(cmd);
+        }
+
+        final PrintWriter pw = shell.getOutPrintWriter();
+        try {
+            switch(cmd) {
+                case "list":
+                    return runList(pw);
+            }
+        } catch (RemoteException e) {
+            pw.println("Remote exception: " + e);
+        }
+        return -1;
+    }
+
+    private int runList(PrintWriter pw) throws RemoteException {
+        final IActivityManager am = ActivityManagerNative.getDefault();
+        final List<UserInfo> users = getUsers(false);
+        if (users == null) {
+            pw.println("Error: couldn't get users");
+            return 1;
+        } else {
+            pw.println("Users:");
+            for (int i = 0; i < users.size(); i++) {
+                String running = am.isUserRunning(users.get(i).id, false) ? " running" : "";
+                pw.println("\t" + users.get(i).toString() + running);
+            }
+            return 0;
+        }
+    }
+
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -2168,38 +2269,49 @@
         long now = System.currentTimeMillis();
         StringBuilder sb = new StringBuilder();
         synchronized (mPackagesLock) {
-            pw.println("Users:");
-            for (int i = 0; i < mUsers.size(); i++) {
-                UserInfo user = mUsers.valueAt(i);
-                if (user == null) continue;
-                pw.print("  "); pw.print(user); pw.print(" serialNo="); pw.print(user.serialNumber);
-                if (mRemovingUserIds.get(mUsers.keyAt(i))) pw.print(" <removing> ");
-                if (user.partial) pw.print(" <partial>");
-                pw.println();
-                pw.print("    Created: ");
-                if (user.creationTime == 0) {
-                    pw.println("<unknown>");
-                } else {
-                    sb.setLength(0);
-                    TimeUtils.formatDuration(now - user.creationTime, sb);
-                    sb.append(" ago");
-                    pw.println(sb);
+            synchronized (mUsersLock) {
+                pw.println("Users:");
+                for (int i = 0; i < mUsers.size(); i++) {
+                    UserInfo user = mUsers.valueAt(i);
+                    if (user == null) {
+                        continue;
+                    }
+                    pw.print("  "); pw.print(user);
+                    pw.print(" serialNo="); pw.print(user.serialNumber);
+                    if (mRemovingUserIds.get(mUsers.keyAt(i))) {
+                        pw.print(" <removing> ");
+                    }
+                    if (user.partial) {
+                        pw.print(" <partial>");
+                    }
+                    pw.println();
+                    pw.print("    Created: ");
+                    if (user.creationTime == 0) {
+                        pw.println("<unknown>");
+                    } else {
+                        sb.setLength(0);
+                        TimeUtils.formatDuration(now - user.creationTime, sb);
+                        sb.append(" ago");
+                        pw.println(sb);
+                    }
+                    pw.print("    Last logged in: ");
+                    if (user.lastLoggedInTime == 0) {
+                        pw.println("<unknown>");
+                    } else {
+                        sb.setLength(0);
+                        TimeUtils.formatDuration(now - user.lastLoggedInTime, sb);
+                        sb.append(" ago");
+                        pw.println(sb);
+                    }
+                    pw.println("    Restrictions:");
+                    synchronized (mRestrictionsLock) {
+                        UserRestrictionsUtils.dumpRestrictions(
+                                pw, "      ", mBaseUserRestrictions.get(user.id));
+                        pw.println("    Effective restrictions:");
+                        UserRestrictionsUtils.dumpRestrictions(
+                                pw, "      ", mCachedEffectiveUserRestrictions.get(user.id));
+                    }
                 }
-                pw.print("    Last logged in: ");
-                if (user.lastLoggedInTime == 0) {
-                    pw.println("<unknown>");
-                } else {
-                    sb.setLength(0);
-                    TimeUtils.formatDuration(now - user.lastLoggedInTime, sb);
-                    sb.append(" ago");
-                    pw.println(sb);
-                }
-                pw.println("    Restrictions:");
-                UserRestrictionsUtils.dumpRestrictions(
-                        pw, "      ", mBaseUserRestrictions.get(user.id));
-                pw.println("    Effective restrictions:");
-                UserRestrictionsUtils.dumpRestrictions(
-                        pw, "      ", mCachedEffectiveUserRestrictions.get(user.id));
             }
             pw.println();
             pw.println("Guest restrictions:");
@@ -2216,9 +2328,9 @@
                     removeMessages(WRITE_USER_MSG, msg.obj);
                     synchronized (mPackagesLock) {
                         int userId = ((UserInfo) msg.obj).id;
-                        UserInfo userInfo = mUsers.get(userId);
+                        UserInfo userInfo = getUserInfoNoChecks(userId);
                         if (userInfo != null) {
-                            writeUserLocked(userInfo);
+                            writeUserLP(userInfo);
                         }
                     }
             }
@@ -2242,14 +2354,14 @@
 
         @Override
         @GuardedBy("mRestrictionsLock")
-        public void updateEffectiveUserRestrictionsRL(int userId) {
-            UserManagerService.this.updateEffectiveUserRestrictionsRL(userId);
+        public void updateEffectiveUserRestrictionsLR(int userId) {
+            UserManagerService.this.updateEffectiveUserRestrictionsLR(userId);
         }
 
         @Override
         @GuardedBy("mRestrictionsLock")
-        public void updateEffectiveUserRestrictionsForAllUsersRL() {
-            UserManagerService.this.updateEffectiveUserRestrictionsForAllUsersRL();
+        public void updateEffectiveUserRestrictionsForAllUsersLR() {
+            UserManagerService.this.updateEffectiveUserRestrictionsForAllUsersLR();
         }
 
         @Override
@@ -2264,17 +2376,35 @@
                 int userId, Bundle baseRestrictions) {
             synchronized (mRestrictionsLock) {
                 mBaseUserRestrictions.put(userId, new Bundle(baseRestrictions));
-                invalidateEffectiveUserRestrictionsRL(userId);
+                invalidateEffectiveUserRestrictionsLR(userId);
             }
 
+            final UserInfo userInfo = getUserInfoNoChecks(userId);
             synchronized (mPackagesLock) {
-                final UserInfo userInfo = mUsers.get(userId);
                 if (userInfo != null) {
-                    writeUserLocked(userInfo);
+                    writeUserLP(userInfo);
                 } else {
                     Slog.w(LOG_TAG, "UserInfo not found for " + userId);
                 }
             }
         }
     }
+
+    private class Shell extends ShellCommand {
+        @Override
+        public int onCommand(String cmd) {
+            return onShellCommand(this, cmd);
+        }
+
+        @Override
+        public void onHelp() {
+            final PrintWriter pw = getOutPrintWriter();
+            pw.println("User manager (user) commands:");
+            pw.println("  help");
+            pw.println("    Print this help text.");
+            pw.println("");
+            pw.println("  list");
+            pw.println("    Prints all users on the system.");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 23e3b35..28df9f6 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -18,10 +18,19 @@
 
 import com.google.android.collect.Sets;
 
-import com.android.internal.util.Preconditions;
-
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.IAudioService;
+import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Slog;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
@@ -31,6 +40,8 @@
 import java.util.Set;
 
 public class UserRestrictionsUtils {
+    private static final String TAG = "UserRestrictionsUtils";
+
     private UserRestrictionsUtils() {
     }
 
@@ -115,6 +126,118 @@
         }
     }
 
+    /**
+     * Takes a new use restriction set and the previous set, and apply the restrictions that have
+     * changed.
+     */
+    public static void applyUserRestrictions(Context context, int userId,
+            @Nullable Bundle newRestrictions, @Nullable Bundle prevRestrictions) {
+        if (newRestrictions == null) {
+            newRestrictions = Bundle.EMPTY;
+        }
+        if (prevRestrictions == null) {
+            prevRestrictions = Bundle.EMPTY;
+        }
+        for (String key : USER_RESTRICTIONS) {
+            final boolean newValue = newRestrictions.getBoolean(key);
+            final boolean prevValue = prevRestrictions.getBoolean(key);
+
+            if (newValue != prevValue) {
+                applyUserRestriction(context, userId, key, newValue);
+            }
+        }
+    }
+
+    private static void applyUserRestriction(Context context, int userId, String key,
+            boolean newValue) {
+        // When certain restrictions are cleared, we don't update the system settings,
+        // because these settings are changeable on the Settings UI and we don't know the original
+        // value -- for example LOCATION_MODE might have been off already when the restriction was
+        // set, and in that case even if the restriction is lifted, changing it to ON would be
+        // wrong.  So just don't do anything in such a case.  If the user hopes to enable location
+        // later, they can do it on the Settings UI.
+
+        final ContentResolver cr = context.getContentResolver();
+        final long id = Binder.clearCallingIdentity();
+        try {
+            switch (key) {
+                case UserManager.DISALLOW_UNMUTE_MICROPHONE:
+                    IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE))
+                            .setMicrophoneMute(newValue, context.getPackageName(), userId);
+                    break;
+                case UserManager.DISALLOW_ADJUST_VOLUME:
+                    IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE))
+                            .setMasterMute(newValue, 0, context.getPackageName(), userId);
+                    break;
+                case UserManager.DISALLOW_CONFIG_WIFI:
+                    if (newValue) {
+                        android.provider.Settings.Secure.putIntForUser(cr,
+                                android.provider.Settings.Secure
+                                        .WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0, userId);
+                    }
+                    break;
+                case UserManager.DISALLOW_SHARE_LOCATION:
+                    if (newValue) {
+                        android.provider.Settings.Secure.putIntForUser(cr,
+                                android.provider.Settings.Secure.LOCATION_MODE,
+                                android.provider.Settings.Secure.LOCATION_MODE_OFF,
+                                userId);
+                        android.provider.Settings.Secure.putStringForUser(cr,
+                                android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
+                                userId);
+                    }
+                    // Send out notifications as some clients may want to reread the
+                    // value which actually changed due to a restriction having been
+                    // applied.
+                    final String property =
+                            android.provider.Settings.Secure.SYS_PROP_SETTING_VERSION;
+                    long version = SystemProperties.getLong(property, 0) + 1;
+                    SystemProperties.set(property, Long.toString(version));
+
+                    final String name = android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
+                    final Uri url = Uri.withAppendedPath(
+                            android.provider.Settings.Secure.CONTENT_URI, name);
+                    context.getContentResolver().notifyChange(url, null, true, userId);
+
+                    break;
+                case UserManager.DISALLOW_DEBUGGING_FEATURES:
+                    if (newValue) {
+                        // Only disable adb if changing for system user, since it is global
+                        // TODO: should this be admin user?
+                        if (userId == UserHandle.USER_SYSTEM) {
+                            android.provider.Settings.Global.putStringForUser(cr,
+                                    android.provider.Settings.Global.ADB_ENABLED, "0",
+                                    userId);
+                        }
+                    }
+                    break;
+                case UserManager.ENSURE_VERIFY_APPS:
+                    if (newValue) {
+                        android.provider.Settings.Global.putStringForUser(
+                                context.getContentResolver(),
+                                android.provider.Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
+                                userId);
+                        android.provider.Settings.Global.putStringForUser(
+                                context.getContentResolver(),
+                                android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
+                                userId);
+                    }
+                    break;
+                case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES:
+                    if (newValue) {
+                        android.provider.Settings.Secure.putIntForUser(cr,
+                                android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
+                                userId);
+                    }
+                    break;
+            }
+        } catch (RemoteException re) {
+            Slog.e(TAG, "Failed to talk to AudioService.", re);
+        } finally {
+            Binder.restoreCallingIdentity(id);
+        }
+    }
+
     public static void dumpRestrictions(PrintWriter pw, String prefix, Bundle restrictions) {
         boolean noneSet = true;
         if (restrictions != null) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b38b9ce..209751e 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -16,8 +16,8 @@
 
 package com.android.server.policy;
 
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.view.WindowManager.LayoutParams.*;
 import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
 import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index d4c5f87..ac79b36 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -40,6 +40,8 @@
     private boolean mRelroReady32Bit = false;
     private boolean mRelroReady64Bit = false;
 
+    private String oldWebViewPackageName = null;
+
     private BroadcastReceiver mWebViewUpdatedReceiver;
 
     public WebViewUpdateService(Context context) {
@@ -51,9 +53,22 @@
         mWebViewUpdatedReceiver = new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
-                    String webviewPackage = "package:" + WebViewFactory.getWebViewPackageName();
-                    if (webviewPackage.equals(intent.getDataString())) {
-                        onWebViewUpdateInstalled();
+
+                    for (String packageName : WebViewFactory.getWebViewPackageNames()) {
+                        String webviewPackage = "package:" + packageName;
+
+                        if (webviewPackage.equals(intent.getDataString())) {
+                            String usedPackageName =
+                                WebViewFactory.findPreferredWebViewPackage().packageName;
+                            // Only trigger update actions if the updated package is the one that
+                            // will be used, or the one that was in use before the update.
+                            if (packageName.equals(usedPackageName) ||
+                                    packageName.equals(oldWebViewPackageName)) {
+                                onWebViewUpdateInstalled();
+                                oldWebViewPackageName = usedPackageName;
+                            }
+                            return;
+                        }
                     }
                 }
         };
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index c5bd3a7..9143097 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -102,6 +102,7 @@
     // Set to true when the token has been removed from the window mgr.
     boolean removed;
 
+    boolean appDied;
     // Information about an application starting window if displayed.
     StartingData startingData;
     WindowState startingWindow;
@@ -365,6 +366,26 @@
         windows.clear();
     }
 
+    void removeAllDeadWindows() {
+        for (int winNdx = allAppWindows.size() - 1; winNdx >= 0;
+                // removeWindowLocked at bottom of loop may remove multiple entries from
+                // allAppWindows if the window to be removed has child windows. It also may
+                // not remove any windows from allAppWindows at all if win is exiting and
+                // currently animating away. This ensures that winNdx is monotonically decreasing
+                // and never beyond allAppWindows bounds.
+                winNdx = Math.min(winNdx - 1, allAppWindows.size() - 1)) {
+            WindowState win = allAppWindows.get(winNdx);
+            if (win.mAppDied) {
+                if (WindowManagerService.DEBUG_WINDOW_MOVEMENT) {
+                    Slog.w(WindowManagerService.TAG, "removeAllDeadWindows: " + win);
+                }
+                // Set mDestroying, we don't want any animation or delayed removal here.
+                win.mDestroying = true;
+                service.removeWindowLocked(win);
+            }
+        }
+    }
+
     @Override
     void dump(PrintWriter pw, String prefix) {
         super.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/DimBehindController.java b/services/core/java/com/android/server/wm/DimLayerController.java
similarity index 78%
rename from services/core/java/com/android/server/wm/DimBehindController.java
rename to services/core/java/com/android/server/wm/DimLayerController.java
index 8870dd1..f9aca00 100644
--- a/services/core/java/com/android/server/wm/DimBehindController.java
+++ b/services/core/java/com/android/server/wm/DimLayerController.java
@@ -1,6 +1,7 @@
 package com.android.server.wm;
 
 import static com.android.server.wm.WindowManagerService.DEBUG_DIM_LAYER;
+import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
 
 import android.graphics.Rect;
 import android.util.ArrayMap;
@@ -11,32 +12,38 @@
 
 /**
  * Centralizes the control of dim layers used for
- * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}.
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
+ * as well as other use cases (such as dimming above a dead window).
  */
-class DimBehindController {
-    private static final String TAG = "DimBehindController";
+class DimLayerController {
+    private static final String TAG = "DimLayerController";
 
     /** Amount of time in milliseconds to animate the dim surface from one value to another,
      * when no window animation is driving it. */
     private static final int DEFAULT_DIM_DURATION = 200;
 
-    // Shared dim layer for fullscreen users. {@link DimBehindState#dimLayer} will point to this
+    /**
+     * The default amount of dim applied over a dead window
+     */
+    private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
+
+    // Shared dim layer for fullscreen users. {@link DimLayerState#dimLayer} will point to this
     // instead of creating a new object per fullscreen task on a display.
     private DimLayer mSharedFullScreenDimLayer;
 
-    private ArrayMap<DimLayer.DimLayerUser, DimBehindState> mState = new ArrayMap<>();
+    private ArrayMap<DimLayer.DimLayerUser, DimLayerState> mState = new ArrayMap<>();
 
     private DisplayContent mDisplayContent;
 
     private Rect mTmpBounds = new Rect();
 
-    DimBehindController(DisplayContent displayContent) {
+    DimLayerController(DisplayContent displayContent) {
         mDisplayContent = displayContent;
     }
 
     /** Updates the dim layer bounds, recreating it if needed. */
     void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
-        DimBehindState state = getOrCreateDimBehindState(dimLayerUser);
+        DimLayerState state = getOrCreateDimLayerState(dimLayerUser, false);
         final boolean previousFullscreen = state.dimLayer != null
                 && state.dimLayer == mSharedFullScreenDimLayer;
         DimLayer newDimLayer;
@@ -60,7 +67,7 @@
                 newDimLayer.setBounds(mTmpBounds);
                 mSharedFullScreenDimLayer = newDimLayer;
             } else if (state.dimLayer != null) {
-                state.dimLayer. destroySurface();
+                state.dimLayer.destroySurface();
             }
         } else {
             newDimLayer = (state.dimLayer == null || previousFullscreen)
@@ -72,19 +79,21 @@
         state.dimLayer = newDimLayer;
     }
 
-    private DimBehindState getOrCreateDimBehindState(DimLayer.DimLayerUser dimLayerUser) {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "getDimBehindState, dimLayerUser="
+    private DimLayerState getOrCreateDimLayerState(
+            DimLayer.DimLayerUser dimLayerUser, boolean aboveApp) {
+        if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser="
                 + dimLayerUser.toShortString());
-        DimBehindState state = mState.get(dimLayerUser);
+        DimLayerState state = mState.get(dimLayerUser);
         if (state == null) {
-            state = new DimBehindState();
+            state = new DimLayerState();
             mState.put(dimLayerUser, state);
         }
+        state.dimAbove = aboveApp;
         return state;
     }
 
     private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
-        DimBehindState state = mState.get(dimLayerUser);
+        DimLayerState state = mState.get(dimLayerUser);
         if (state == null) {
             if (DEBUG_DIM_LAYER) Slog.w(TAG, "setContinueDimming, no state for: "
                     + dimLayerUser.toShortString());
@@ -95,7 +104,7 @@
 
     boolean isDimming() {
         for (int i = mState.size() - 1; i >= 0; i--) {
-            DimBehindState state = mState.valueAt(i);
+            DimLayerState state = mState.valueAt(i);
             if (state.dimLayer != null && state.dimLayer.isDimming()) {
                 return true;
             }
@@ -110,15 +119,15 @@
     }
 
     private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
-        DimBehindState state = mState.get(dimLayerUser);
+        DimLayerState state = mState.get(dimLayerUser);
         return state != null && state.continueDimming;
     }
 
     void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser,
-            WindowStateAnimator newWinAnimator) {
+            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.
-        DimBehindState state = getOrCreateDimBehindState(dimLayerUser);
+        DimLayerState state = getOrCreateDimLayerState(dimLayerUser, aboveApp);
         if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
                 + " dimLayerUser=" + dimLayerUser.toShortString()
                 + " newWinAnimator=" + newWinAnimator
@@ -145,7 +154,7 @@
 
     private void stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser) {
         // No need to check if state is null, we know the key has a value.
-        DimBehindState state = mState.get(dimLayerUser);
+        DimLayerState state = mState.get(dimLayerUser);
         if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded,"
                 + " dimLayerUser=" + dimLayerUser.toShortString()
                 + " state.continueDimming=" + state.continueDimming
@@ -188,7 +197,7 @@
     }
 
     private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
-        DimBehindState state = mState.get(dimLayerUser);
+        DimLayerState state = mState.get(dimLayerUser);
         if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers,"
                 + " dimLayerUser=" + dimLayerUser.toShortString()
                 + " state.animator=" + state.animator
@@ -199,8 +208,13 @@
             dimLayer = state.dimLayer.getLayer();
             dimAmount = 0;
         } else {
-            dimLayer = state.animator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM;
-            dimAmount = state.animator.mWin.mAttrs.dimAmount;
+            if (state.dimAbove) {
+                dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM;
+                dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW;
+            } else {
+                dimLayer = state.animator.mAnimLayer - LAYER_OFFSET_DIM;
+                dimAmount = state.animator.mWin.mAttrs.dimAmount;
+            }
         }
         final float targetAlpha = state.dimLayer.getTargetAlpha();
         if (targetAlpha != dimAmount) {
@@ -211,7 +225,7 @@
                         ? state.animator.mAnimation.computeDurationHint()
                         : DEFAULT_DIM_DURATION;
                 if (targetAlpha > dimAmount) {
-                    duration = getDimBehindFadeDuration(duration);
+                    duration = getDimLayerFadeDuration(duration);
                 }
                 state.dimLayer.show(dimLayer, dimAmount, duration);
             }
@@ -230,11 +244,11 @@
     }
 
     boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
-        DimBehindState state = mState.get(dimLayerUser);
+        DimLayerState state = mState.get(dimLayerUser);
         return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
     }
 
-    private long getDimBehindFadeDuration(long duration) {
+    private long getDimLayerFadeDuration(long duration) {
         TypedValue tv = new TypedValue();
         mDisplayContent.mService.mContext.getResources().getValue(
                 com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
@@ -248,7 +262,7 @@
 
     void close() {
         for (int i = mState.size() - 1; i >= 0; i--) {
-            DimBehindState state = mState.valueAt(i);
+            DimLayerState state = mState.valueAt(i);
             state.dimLayer.destroySurface();
         }
         mState.clear();
@@ -256,10 +270,23 @@
     }
 
     void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
-        mState.remove(dimLayerUser);
+        DimLayerState state = mState.get(dimLayerUser);
+        if (state != null) {
+            state.dimLayer.destroySurface();
+            mState.remove(dimLayerUser);
+        }
     }
 
     void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
+        applyDim(dimLayerUser, animator, false /* aboveApp */);
+    }
+
+    void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
+        applyDim(dimLayerUser, animator, true /* aboveApp */);
+    }
+
+    private void applyDim(
+            DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) {
         if (dimLayerUser == null) {
             Slog.e(TAG, "Trying to apply dim layer for: " + this
                     + ", but no dim layer user found.");
@@ -269,26 +296,27 @@
             setContinueDimming(dimLayerUser);
             if (!isDimming(dimLayerUser, animator)) {
                 if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
-                startDimmingIfNeeded(dimLayerUser, animator);
+                startDimmingIfNeeded(dimLayerUser, animator, aboveApp);
             }
         }
     }
 
-    private static class DimBehindState {
-        // The particular window with FLAG_DIM_BEHIND set. If null, hide dimLayer.
+    private static class DimLayerState {
+        // The particular window requesting a dim layer. If null, hide dimLayer.
         WindowStateAnimator animator;
         // Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the
         // end then stop any dimming.
         boolean continueDimming;
         DimLayer dimLayer;
+        boolean dimAbove;
     }
 
     void dump(String prefix, PrintWriter pw) {
-        pw.println(prefix + "DimBehindController");
+        pw.println(prefix + "DimLayerController");
         for (int i = 0, n = mState.size(); i < n; i++) {
             pw.println(prefix + "  " + mState.keyAt(i).toShortString());
             pw.print(prefix + "    ");
-            DimBehindState state = mState.valueAt(i);
+            DimLayerState state = mState.valueAt(i);
             pw.print("dimLayer=" + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" :
                     state.dimLayer));
             pw.print(", animator=" + state.animator);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fab8ee5..53f8bbd 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -16,14 +16,15 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerService.TAG;
 import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
 import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
 
+import android.app.ActivityManager.StackId;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.util.DisplayMetrics;
@@ -115,7 +116,7 @@
 
     final DockedStackDividerController mDividerControllerLocked;
 
-    final DimBehindController mDimBehindController;
+    final DimLayerController mDimLayerController;
 
     /**
      * @param display May not be null.
@@ -130,7 +131,7 @@
         mService = service;
         initializeDisplayBaseInfo();
         mDividerControllerLocked = new DockedStackDividerController(service.mContext, this);
-        mDimBehindController = new DimBehindController(this);
+        mDimLayerController = new DimLayerController(this);
     }
 
     int getDisplayId() {
@@ -270,7 +271,7 @@
     }
 
     void detachStack(TaskStack stack) {
-        mDimBehindController.removeDimLayerUser(stack);
+        mDimLayerController.removeDimLayerUser(stack);
         mStacks.remove(stack);
     }
 
@@ -311,7 +312,7 @@
         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);
-            if (!stack.allowTaskResize()) {
+            if (!StackId.isTaskResizeAllowed(stack.mStackId)) {
                 break;
             }
             final ArrayList<Task> tasks = stack.getTasks();
@@ -414,23 +415,23 @@
     }
 
     boolean animateDimLayers() {
-        return mDimBehindController.animateDimLayers();
+        return mDimLayerController.animateDimLayers();
     }
 
     void resetDimming() {
-        mDimBehindController.resetDimming();
+        mDimLayerController.resetDimming();
     }
 
     boolean isDimming() {
-        return mDimBehindController.isDimming();
+        return mDimLayerController.isDimming();
     }
 
     void stopDimmingIfNeeded() {
-        mDimBehindController.stopDimmingIfNeeded();
+        mDimLayerController.stopDimmingIfNeeded();
     }
 
     void close() {
-        mDimBehindController.close();
+        mDimLayerController.close();
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             mStacks.get(stackNdx).close();
         }
@@ -577,7 +578,7 @@
             }
         }
         pw.println();
-        mDimBehindController.dump(prefix + "  ", pw);
+        mDimLayerController.dump(prefix + "  ", pw);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 04cba81..eafc3c6 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW;
 import static android.view.PointerIcon.STYLE_VERTICAL_DOUBLE_ARROW;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1f62bc1..c47c377 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -187,6 +187,12 @@
         mService.removeWindow(this, window);
     }
 
+    @Override
+    public void repositionChild(IWindow window, int x, int y, long deferTransactionUntilFrame,
+             Rect outFrame) {
+        mService.repositionChild(this, window, x, y, deferTransactionUntilFrame, outFrame);
+    }
+
     public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
             int requestedWidth, int requestedHeight, int viewFlags,
             int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ad44196..5864b25 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -16,22 +16,21 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
 import static com.android.server.wm.WindowManagerService.TAG;
 import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
 import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
 import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
 
-
+import android.app.ActivityManager.StackId;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.util.EventLog;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.view.DisplayInfo;
 import android.view.Surface;
 
@@ -117,7 +116,7 @@
         mDeferRemoval = false;
         DisplayContent content = getDisplayContent();
         if (content != null) {
-            content.mDimBehindController.removeDimLayerUser(this);
+            content.mDimLayerController.removeDimLayerUser(this);
         }
         mStack.removeTask(this);
         mService.mTaskIdToTask.delete(mTaskId);
@@ -221,7 +220,7 @@
         mBounds.set(bounds);
         mRotation = rotation;
         if (displayContent != null) {
-            displayContent.mDimBehindController.updateDimLayer(this);
+            displayContent.mDimLayerController.updateDimLayer(this);
         }
         mOverrideConfig = mFullscreen ? Configuration.EMPTY : config;
         return boundsChange;
@@ -245,7 +244,7 @@
     private boolean useCurrentBounds() {
         final DisplayContent displayContent = mStack.getDisplayContent();
         if (mFullscreen
-                || mStack.allowTaskResize()
+                || !StackId.isTaskResizeableByDockedStack(mStack.mStackId)
                 || displayContent == null
                 || displayContent.getDockedStackLocked() != null) {
             return true;
@@ -320,6 +319,15 @@
         }
     }
 
+    /**
+     * Cancels any running app transitions associated with the task.
+     */
+    void cancelTaskWindowTransition() {
+        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
+            mAppTokens.get(activityNdx).mAppAnimator.clearAnimation();
+        }
+    }
+
     boolean showForAllUsers() {
         final int tokensCount = mAppTokens.size();
         return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers;
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 227b3f0..aae3bd2 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -18,7 +18,7 @@
 
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.RESIZE_MODE_USER;
 import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index df664bd..f72384c 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -17,17 +17,16 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FIRST_STATIC_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.LAST_STATIC_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerService.H.RESIZE_STACK;
 import static com.android.server.wm.WindowManagerService.TAG;
 
 import android.annotation.IntDef;
+import android.app.ActivityManager.StackId;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Debug;
@@ -122,12 +121,6 @@
         }
     }
 
-    boolean allowTaskResize() {
-        return mStackId == FREEFORM_WORKSPACE_STACK_ID
-                || mStackId == DOCKED_STACK_ID
-                || mStackId == PINNED_STACK_ID;
-    }
-
     /**
      * Set the bounds of the stack and its containing tasks.
      * @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen.
@@ -183,7 +176,7 @@
         }
 
         if (mDisplayContent != null) {
-            mDisplayContent.mDimBehindController.updateDimLayer(this);
+            mDisplayContent.mDimLayerController.updateDimLayer(this);
             mAnimationBackgroundSurface.setBounds(bounds);
         }
 
@@ -203,8 +196,7 @@
     /** Return true if the current bound can get outputted to the rest of the system as-is. */
     private boolean useCurrentBounds() {
         if (mFullscreen
-                || mStackId == DOCKED_STACK_ID
-                || mStackId == PINNED_STACK_ID
+                || !StackId.isResizeableByDockedStack(mStackId)
                 || mDisplayContent == null
                 || mDisplayContent.getDockedStackLocked() != null) {
             return true;
@@ -396,9 +388,9 @@
 
         Rect bounds = null;
         final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
-        if (mStackId == DOCKED_STACK_ID || (dockedStack != null && mStackId != PINNED_STACK_ID
-                && mStackId >= FIRST_STATIC_STACK_ID && mStackId <= LAST_STATIC_STACK_ID)) {
-            // The existence of a docked stack affects the size of any static stack created since
+        if (mStackId == DOCKED_STACK_ID
+                || (dockedStack != null && StackId.isResizeableByDockedStack(mStackId))) {
+            // The existence of a docked stack affects the size of other static stack created since
             // the docked stack occupies a dedicated region on screen.
             bounds = new Rect();
             displayContent.getLogicalDisplayRect(mTmpRect);
@@ -424,10 +416,7 @@
     }
 
     void getStackDockedModeBoundsLocked(Rect outBounds) {
-        if (mStackId == DOCKED_STACK_ID
-                || mStackId == PINNED_STACK_ID
-                || mStackId > LAST_STATIC_STACK_ID
-                || mDisplayContent == null) {
+        if (!StackId.isResizeableByDockedStack(mStackId) || mDisplayContent == null) {
             outBounds.set(mBounds);
             return;
         }
@@ -537,9 +526,7 @@
         for (int i = 0; i < count; i++) {
             final TaskStack otherStack = mService.mStackIdToStack.valueAt(i);
             final int otherStackId = otherStack.mStackId;
-            if (otherStackId != DOCKED_STACK_ID && mStackId != PINNED_STACK_ID
-                    && otherStackId >= FIRST_STATIC_STACK_ID
-                    && otherStackId <= LAST_STATIC_STACK_ID) {
+            if (StackId.isResizeableByDockedStack(otherStackId)) {
                 mService.mH.sendMessage(
                         mService.mH.obtainMessage(RESIZE_STACK, otherStackId,
                                 1 /*allowResizeInDockedMode*/, bounds));
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 7f2f2cd..4a9d8cb 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 75680a0..d5304c2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -17,8 +17,8 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -1924,6 +1924,11 @@
                 return WindowManagerGlobal.ADD_INVALID_DISPLAY;
             }
 
+            if (atoken != null && atoken.appDied) {
+                Slog.d(TAG, "App is now revived: " + atoken);
+                atoken.appDied = false;
+            }
+
             mPolicy.adjustWindowParamsLw(win.mAttrs);
             win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
 
@@ -1935,11 +1940,7 @@
             final boolean openInputChannels = (outInputChannel != null
                     && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
             if  (openInputChannels) {
-                String name = win.makeInputChannelName();
-                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
-                win.setInputChannel(inputChannels[0]);
-                inputChannels[1].transferTo(outInputChannel);
-                mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
+                win.openInputChannel(outInputChannel);
             }
 
             // From now on, no exceptions or errors allowed!
@@ -2183,13 +2184,31 @@
                         + "added");
                 win.mExiting = true;
                 appToken.mReplacingRemoveRequested = true;
+                Binder.restoreCallingIdentity(origId);
                 return;
             }
             // If we are not currently running the exit animation, we
             // need to see about starting one.
             wasVisible = win.isWinVisibleLw();
-            if (wasVisible) {
 
+            if (wasVisible && appToken != null && appToken.appDied) {
+                if (DEBUG_ADD_REMOVE) Slog.v(TAG,
+                        "Not removing " + win + " because app died while it's visible");
+
+                win.mAppDied = true;
+                win.setDisplayLayoutNeeded();
+                mWindowPlacerLocked.performSurfacePlacement();
+
+                // Set up a replacement input channel since the app is now dead.
+                // We need to catch tapping on the dead window to restart the app.
+                win.openInputChannel(null);
+                mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+                Binder.restoreCallingIdentity(origId);
+                return;
+            }
+
+            if (wasVisible) {
                 final int transit = (!startingWindow)
                         ? WindowManagerPolicy.TRANSIT_EXIT
                         : WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
@@ -2240,10 +2259,6 @@
     }
 
     void removeWindowInnerLocked(WindowState win) {
-        removeWindowInnerLocked(win, true);
-    }
-
-    private void removeWindowInnerLocked(WindowState win, boolean performLayout) {
         if (win.mRemoved) {
             // Nothing to do.
             return;
@@ -2338,9 +2353,7 @@
             if (!mWindowPlacerLocked.isInLayout()) {
                 assignLayersLocked(windows);
                 win.setDisplayLayoutNeeded();
-                if (performLayout) {
-                    mWindowPlacerLocked.performSurfacePlacement();
-                }
+                mWindowPlacerLocked.performSurfacePlacement();
                 if (win.mAppToken != null) {
                     win.mAppToken.updateReportedVisibilityLocked();
                 }
@@ -2468,6 +2481,54 @@
         }
     }
 
+    void repositionChild(Session session, IWindow client,
+            int x, int y, long deferTransactionUntilFrame, Rect outFrame) {
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "repositionChild");
+        long origId = Binder.clearCallingIdentity();
+
+        try {
+            synchronized(mWindowMap) {
+                WindowState win = windowForClientLocked(session, client, false);
+                if (win == null) {
+                    return;
+                }
+                if (win.mAttachedWindow == null) {
+                    throw new IllegalArgumentException(
+                            "repositionChild called but window is not"
+                            + "attached to a parent win=" + win);
+                }
+
+                win.mFrame.left = x;
+                win.mFrame.top = y;
+
+                win.mWinAnimator.computeShownFrameLocked();
+
+                if (SHOW_TRANSACTIONS) {
+                    Slog.i(TAG, ">>> OPEN TRANSACTION repositionChild");
+                }
+
+                SurfaceControl.openTransaction();
+
+                if (deferTransactionUntilFrame > 0) {
+                    win.mWinAnimator.mSurfaceControl.deferTransactionUntil(
+                            win.mAttachedWindow.mWinAnimator.mSurfaceControl.getHandle(),
+                            deferTransactionUntilFrame);
+                }
+                win.mWinAnimator.setSurfaceBoundariesLocked(false);
+
+                SurfaceControl.closeTransaction();
+                if (SHOW_TRANSACTIONS) {
+                    Slog.i(TAG, "<<< CLOSE TRANSACTION repositionChild");
+                }
+
+                outFrame = win.mCompatFrame;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
     public int relayoutWindow(Session session, IWindow client, int seq,
             WindowManager.LayoutParams attrs, int requestedWidth,
             int requestedHeight, int viewVisibility, int flags,
@@ -4070,6 +4131,14 @@
             wtoken.waitingToShow = false;
             wtoken.hiddenRequested = !visible;
 
+            if (!visible && wtoken.appDied) {
+                // This app is dead while it was visible, we kept its dead window on screen.
+                // Now that the app is going invisible, we can remove it. It will be restarted
+                // if made visible again.
+                wtoken.appDied = false;
+                wtoken.removeAllWindows();
+            }
+
             // If we are preparing an app transition, then delay changing
             // the visibility of this token until we execute that transition.
             if (okToDisplay() && mAppTransition.isTransitionSet()) {
@@ -4607,6 +4676,16 @@
         }
     }
 
+    @Override
+    public void cancelTaskWindowTransition(int taskId) {
+        synchronized (mWindowMap) {
+            Task task = mTaskIdToTask.get(taskId);
+            if (task != null) {
+                task.cancelTaskWindowTransition();
+            }
+        }
+    }
+
     public void addTask(int taskId, int stackId, boolean toTop) {
         synchronized (mWindowMap) {
             if (DEBUG_STACK) Slog.i(TAG, "addTask: adding taskId=" + taskId
@@ -8490,7 +8569,7 @@
             final DimLayer.DimLayerUser dimLayerUser = w.getDimLayerUser();
             final DisplayContent displayContent = w.getDisplayContent();
             if (layerChanged && dimLayerUser != null && displayContent != null &&
-                    displayContent.mDimBehindController.isDimming(dimLayerUser, winAnimator)) {
+                    displayContent.mDimLayerController.isDimming(dimLayerUser, winAnimator)) {
                 // Force an animation pass just to update the mDimLayer layer.
                 scheduleAnimationLocked();
             }
@@ -8609,6 +8688,13 @@
                             + " dragResizingChanged=" + dragResizingChanged);
                 }
 
+                // If it's a dead window left on screen, and the configuration changed,
+                // there is nothing we can do about it. Remove the window now.
+                if (w.mAppToken != null && w.mAppDied) {
+                    w.mAppToken.removeAllDeadWindows();
+                    return;
+                }
+
                 w.mLastOverscanInsets.set(w.mOverscanInsets);
                 w.mLastContentInsets.set(w.mContentInsets);
                 w.mLastVisibleInsets.set(w.mVisibleInsets);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5bc329e..984a353 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -340,6 +340,12 @@
     boolean mRemoveOnExit;
 
     /**
+     * Whether the app died while it was visible, if true we might need
+     * to continue to show it until it's restarted.
+     */
+    boolean mAppDied;
+
+    /**
      * Set when the orientation is changing and this window has not yet
      * been updated for the new orientation.
      */
@@ -362,6 +368,7 @@
     // Input channel and input window handle used by the input dispatcher.
     final InputWindowHandle mInputWindowHandle;
     InputChannel mInputChannel;
+    InputChannel mClientChannel;
 
     // Used to improve performance of toString()
     String mStringNameCache;
@@ -1274,16 +1281,28 @@
         mConfigHasChanged = false;
     }
 
-    void setInputChannel(InputChannel inputChannel) {
+    void openInputChannel(InputChannel outInputChannel) {
         if (mInputChannel != null) {
             throw new IllegalStateException("Window already has an input channel.");
         }
-
-        mInputChannel = inputChannel;
-        mInputWindowHandle.inputChannel = inputChannel;
+        String name = makeInputChannelName();
+        InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
+        mInputChannel = inputChannels[0];
+        mClientChannel = inputChannels[1];
+        mInputWindowHandle.inputChannel = inputChannels[0];
+        if (outInputChannel != null) {
+            mClientChannel.transferTo(outInputChannel);
+            mClientChannel.dispose();
+            mClientChannel = null;
+        }
+        mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
     }
 
     void disposeInputChannel() {
+        if (mClientChannel != null) {
+            mClientChannel.dispose();
+            mClientChannel = null;
+        }
         if (mInputChannel != null) {
             mService.mInputManager.unregisterInputChannel(mInputChannel);
 
@@ -1294,10 +1313,13 @@
         mInputWindowHandle.inputChannel = null;
     }
 
-    void handleFlagDimBehind() {
-        if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0 && mDisplayContent != null && !mExiting
-                && isDisplayedLw()) {
-            mDisplayContent.mDimBehindController.applyDimBehind(getDimLayerUser(), mWinAnimator);
+    void applyDimLayerIfNeeded() {
+        if (!mExiting && mAppDied) {
+            // If app died visible, apply a dim over the window to indicate that it's inactive
+            mDisplayContent.mDimLayerController.applyDimAbove(getDimLayerUser(), mWinAnimator);
+        } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
+                && mDisplayContent != null && !mExiting && isDisplayedLw()) {
+            mDisplayContent.mDimLayerController.applyDimBehind(getDimLayerUser(), mWinAnimator);
         }
     }
 
@@ -1375,6 +1397,9 @@
                     WindowState win = mService.windowForClientLocked(mSession, mClient, false);
                     Slog.i(TAG, "WIN DEATH: " + win);
                     if (win != null) {
+                        if (win.mAppToken != null && !win.mAppToken.clientHidden) {
+                            win.mAppToken.appDied = true;
+                        }
                         mService.removeWindowLocked(win);
                     } else if (mHasSurface) {
                         Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
@@ -1574,7 +1599,7 @@
     public boolean isDimming() {
         final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
         return dimLayerUser != null && mDisplayContent != null &&
-                mDisplayContent.mDimBehindController.isDimming(dimLayerUser, mWinAnimator);
+                mDisplayContent.mDimLayerController.isDimming(dimLayerUser, mWinAnimator);
     }
 
     public void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
@@ -1833,7 +1858,8 @@
             pw.print(prefix); pw.print("mToken="); pw.println(mToken);
             pw.print(prefix); pw.print("mRootToken="); pw.println(mRootToken);
             if (mAppToken != null) {
-                pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken);
+                pw.print(prefix); pw.print("mAppToken="); pw.print(mAppToken);
+                pw.print(" mAppDied=");pw.println(mAppDied);
             }
             if (mTargetAppToken != null) {
                 pw.print(prefix); pw.print("mTargetAppToken="); pw.println(mTargetAppToken);
@@ -1999,4 +2025,13 @@
         }
         return mStringNameCache;
     }
+
+    void transformFromScreenToSurfaceSpace(Rect rect) {
+         if (mHScale >= 0) {
+            rect.right = rect.left + (int)((rect.right - rect.left) / mHScale);
+        }
+        if (mVScale >= 0) {
+            rect.bottom = rect.top + (int)((rect.bottom - rect.top) / mVScale);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index aa242f1..e9be2dd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1223,14 +1223,6 @@
                     mShownAlpha *= appTransformation.getAlpha();
                     if (appTransformation.hasClipRect()) {
                         mClipRect.set(appTransformation.getClipRect());
-                        if (mWin.mHScale > 0) {
-                            mClipRect.left /= mWin.mHScale;
-                            mClipRect.right /= mWin.mHScale;
-                        }
-                        if (mWin.mVScale > 0) {
-                            mClipRect.top /= mWin.mVScale;
-                            mClipRect.bottom /= mWin.mVScale;
-                        }
                         mHasClipRect = true;
                     }
                 }
@@ -1350,11 +1342,7 @@
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
 
         // Need to recompute a new system decor rect each time.
-        if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
-            // Currently can't do this cropping for scaled windows.  We'll
-            // just keep the crop rect the same as the source surface.
-            w.mSystemDecorRect.set(0, 0, w.mRequestedWidth, w.mRequestedHeight);
-        } else if (!w.isDefaultDisplay()) {
+        if (!w.isDefaultDisplay()) {
             // On a different display there is no system decor.  Crop the window
             // by the screen boundaries.
             w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
@@ -1407,9 +1395,13 @@
         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);
         }
+
+        w.transformFromScreenToSurfaceSpace(clipRect);
+
         if (!clipRect.equals(mLastClipRect)) {
             mLastClipRect.set(clipRect);
             try {
@@ -1434,12 +1426,13 @@
 
     private void adjustCropToStackBounds(WindowState w, Rect clipRect) {
         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 (appToken != null && appToken.mCropWindowsToStack && !appToken.mWillReplaceWindow) {
-            TaskStack stack = w.getTask().mStack;
+        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
@@ -1551,7 +1544,7 @@
                         mDsDy * w.mHScale, mDtDy * w.mVScale);
                 mAnimator.setPendingLayoutChanges(w.getDisplayId(),
                         WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
-                w.handleFlagDimBehind();
+                w.applyDimLayerIfNeeded();
             } catch (RuntimeException e) {
                 // If something goes wrong with the surface (such
                 // as running out of memory), don't take down the
@@ -1867,6 +1860,9 @@
             if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING && mWin.mAppToken != null) {
                 mWin.mAppToken.firstWindowDrawn = true;
 
+                // We now have a good window to show, remove dead placeholders
+                mWin.mAppToken.removeAllDeadWindows();
+
                 if (mWin.mAppToken.startingData != null) {
                     if (WindowManagerService.DEBUG_STARTING_WINDOW ||
                             WindowManagerService.DEBUG_ANIM) Slog.v(TAG,
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index aca0f5b..eef8e17 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -670,7 +670,7 @@
                     handleNotObscuredLocked(w, innerDw, innerDh);
                 }
 
-                w.handleFlagDimBehind();
+                w.applyDimLayerIfNeeded();
 
                 if (isDefaultDisplay && obscuredChanged
                         && mWallpaperControllerLocked.isWallpaperTarget(w) && w.isVisibleLw()) {
@@ -781,7 +781,7 @@
                                         + " a=" + winAnimator.mAnimating);
                             }
                         }
-                        if (w != atoken.startingWindow) {
+                        if (w != atoken.startingWindow && !w.mAppDied) {
                             if (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing) {
                                 atoken.numInterestingWindows++;
                                 if (w.isDrawnLw()) {
diff --git a/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbMidiDevice.cpp
index 06b9bc3..e12a016 100644
--- a/services/core/jni/com_android_server_UsbMidiDevice.cpp
+++ b/services/core/jni/com_android_server_UsbMidiDevice.cpp
@@ -43,12 +43,26 @@
         jint card, jint device)
 {
     char    path[100];
+    int     fd;
+    const   int kMaxRetries = 10;
+    const   int kSleepMicroseconds = 2000;
 
     snprintf(path, sizeof(path), "/dev/snd/controlC%d", card);
-    int fd = open(path, O_RDWR);
-    if (fd < 0) {
-        ALOGE("could not open %s", path);
-        return 0;
+    // This control device may not have been created yet. So we should
+    // try to open it several times to prevent intermittent failure
+    // from a race condition.
+    int retryCounter = 0;
+    while ((fd = open(path, O_RDWR)) < 0) {
+        if (++retryCounter > kMaxRetries) {
+            ALOGE("timed out after %d tries, could not open %s", retryCounter, path);
+            return 0;
+        } else {
+            ALOGW("attempt #%d, could not open %s", retryCounter, path);
+            // Increase the sleep interval each time.
+            // 10 retries will total 2 * sum(1..10) = 110 milliseconds.
+            // Typically the device should be ready in 5-10 milliseconds.
+            usleep(kSleepMicroseconds * retryCounter);
+        }
     }
 
     struct snd_rawmidi_info info;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b4c8f96..aea2ecf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1016,6 +1016,7 @@
         }
     }
 
+    // DO NOT call it while taking the "this" lock, which could cause a dead lock.
     private void handlePackagesChanged(String packageName, int userHandle) {
         boolean removed = false;
         if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
@@ -1042,7 +1043,6 @@
             }
             if (removed) {
                 validatePasswordOwnerLocked(policy);
-                syncDeviceCapabilitiesLocked(policy);
                 saveSettingsLocked(policy.mUserHandle);
             }
 
@@ -1061,6 +1061,13 @@
                 }
             }
         }
+        if (removed) {
+            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+                synchronized (DevicePolicyManagerService.this) {
+                    mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
+                }
+            }
+        }
     }
 
     /**
@@ -1682,7 +1689,7 @@
         }
     }
 
-    void removeActiveAdminLocked(final ComponentName adminReceiver, int userHandle) {
+    void removeActiveAdminLocked(final ComponentName adminReceiver, final int userHandle) {
         final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
         if (admin != null) {
             synchronized (this) {
@@ -1701,7 +1708,6 @@
                                 policy.mAdminList.remove(admin);
                                 policy.mAdminMap.remove(adminReceiver);
                                 validatePasswordOwnerLocked(policy);
-                                syncDeviceCapabilitiesLocked(policy);
                                 if (doProxyCleanup) {
                                     resetGlobalProxyLocked(getUserData(userHandle));
                                 }
@@ -1709,6 +1715,12 @@
                                 updateMaximumTimeToLockLocked(policy);
                                 policy.mRemovingAdmins.remove(adminReceiver);
                             }
+                            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+                                synchronized (DevicePolicyManagerService.this) {
+                                    mUserManagerInternal.updateEffectiveUserRestrictionsLR(
+                                            userHandle);
+                                }
+                            }
                         }
                     });
         }
@@ -2022,7 +2034,6 @@
         }
 
         validatePasswordOwnerLocked(policy);
-        syncDeviceCapabilitiesLocked(policy);
         updateMaximumTimeToLockLocked(policy);
         updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
         if (policy.mStatusBarDisabled) {
@@ -2089,31 +2100,6 @@
         }
     }
 
-    /**
-     * Pushes down policy information to the system for any policies related to general device
-     * capabilities that need to be enforced by lower level services (e.g. Camera services).
-     */
-    void syncDeviceCapabilitiesLocked(DevicePolicyData policy) {
-        // Ensure the status of the camera is synced down to the system. Interested native services
-        // should monitor this value and act accordingly.
-        String cameraPropertyForUser = SYSTEM_PROP_DISABLE_CAMERA_PREFIX + policy.mUserHandle;
-        boolean systemState = mInjector.systemPropertiesGetBoolean(cameraPropertyForUser, false);
-        boolean cameraDisabled = getCameraDisabled(null, policy.mUserHandle);
-        if (cameraDisabled != systemState) {
-            long token = mInjector.binderClearCallingIdentity();
-            try {
-                String value = cameraDisabled ? "1" : "0";
-                if (VERBOSE_LOG) {
-                    Slog.v(LOG_TAG, "Change in camera state ["
-                            + cameraPropertyForUser + "] = " + value);
-                }
-                mInjector.systemPropertiesSet(cameraPropertyForUser, value);
-            } finally {
-                mInjector.binderRestoreCallingIdentity(token);
-            }
-        }
-    }
-
     @VisibleForTesting
     void systemReady(int phase) {
         if (!mHasFeature) {
@@ -4329,13 +4315,6 @@
     }
 
     /**
-     * The system property used to share the state of the camera. The native camera service
-     * is expected to read this property and act accordingly. The userId should be appended
-     * to this key.
-     */
-    public static final String SYSTEM_PROP_DISABLE_CAMERA_PREFIX = "sys.secpolicy.camera.off_";
-
-    /**
      * Disables all device cameras according to the specified admin.
      */
     @Override
@@ -4352,7 +4331,16 @@
                 ap.disableCamera = disabled;
                 saveSettingsLocked(userHandle);
             }
-            syncDeviceCapabilitiesLocked(getUserData(userHandle));
+        }
+        // Tell the user manager that the restrictions have changed.
+        synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+            synchronized (this) {
+                if (isDeviceOwner(who)) {
+                    mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
+                } else {
+                    mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
+                }
+            }
         }
     }
 
@@ -4370,7 +4358,13 @@
                 ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
                 return (admin != null) ? admin.disableCamera : false;
             }
+            // First, see if DO has set it.  If so, it's device-wide.
+            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+            if (deviceOwner != null && deviceOwner.disableCamera) {
+                return true;
+            }
 
+            // Then check each device admin on the user.
             DevicePolicyData policy = getUserData(userHandle);
             // Determine whether or not the device camera is disabled for any active admins.
             final int N = policy.mAdminList.size();
@@ -4404,7 +4398,6 @@
                 ap.disabledKeyguardFeatures = which;
                 saveSettingsLocked(userHandle);
             }
-            syncDeviceCapabilitiesLocked(getUserData(userHandle));
         }
     }
 
@@ -4657,7 +4650,7 @@
                     0  /* flagValues */, userHandle.getIdentifier());
             // TODO This will not revert audio mute restrictions if they were set.  b/24981972
             synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-                mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle.getIdentifier());
+                mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle.getIdentifier());
             }
         } catch (RemoteException re) {
         } finally {
@@ -5036,7 +5029,6 @@
                     DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES);
             ap.trustAgentInfos.put(agent.flattenToString(), new TrustAgentInfo(args));
             saveSettingsLocked(userHandle);
-            syncDeviceCapabilitiesLocked(getUserData(userHandle));
         }
     }
 
@@ -5602,6 +5594,7 @@
         }
     }
 
+    // DO NOT call it while taking the "this" lock, which could cause a dead lock.
     @Override
     public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) {
         Preconditions.checkNotNull(who, "ComponentName is null");
@@ -5612,7 +5605,7 @@
                 ActiveAdmin activeAdmin =
                         getActiveAdminForCallerLocked(who,
                                 DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-                boolean isDeviceOwner = isDeviceOwner(who);
+                final boolean isDeviceOwner = isDeviceOwner(who);
                 if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM
                         && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
                     throw new SecurityException(
@@ -5624,9 +5617,6 @@
 
                 final long id = mInjector.binderClearCallingIdentity();
                 try {
-                    // Original value.
-                    final boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user);
-
                     // Save the restriction to ActiveAdmin.
                     // TODO When DO sets a restriction, it'll always be treated as device-wide.
                     // If there'll be a policy that can be set by both, we'll need scoping support,
@@ -5635,85 +5625,12 @@
                     activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
                     saveSettingsLocked(userHandle);
 
-                    // Tell UserManager the new value.  Note this needs to be done before calling
-                    // into AudioService, because AS will check AppOps that'll be updated by UM.
+                    // Tell UserManager the new value.
                     if (isDeviceOwner) {
-                        mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersRL();
+                        mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
                     } else {
-                        mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle);
+                        mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
                     }
-
-                    // New value.
-                    final boolean enabled = mUserManager.hasUserRestriction(key, user);
-
-                    // TODO The rest of the code should move to UserManagerService.
-
-                    if (enabled && !alreadyRestricted) {
-                        if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
-                            mInjector.getIAudioService()
-                                    .setMicrophoneMute(true, mContext.getPackageName(), userHandle);
-                        } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
-                            mInjector.getIAudioService()
-                                    .setMasterMute(true, 0, mContext.getPackageName(), userHandle);
-                        } else if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) {
-                            mInjector.settingsSecurePutIntForUser(
-                                    Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0,
-                                    userHandle);
-                        } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
-                            mInjector.settingsSecurePutIntForUser(
-                                    Settings.Secure.LOCATION_MODE,
-                                    Settings.Secure.LOCATION_MODE_OFF,
-                                    userHandle);
-                            mInjector.settingsSecurePutStringForUser(
-                                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
-                                    userHandle);
-                        } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) {
-                            // Only disable adb if changing for system user, since it is global
-                            // TODO: should this be admin user?
-                            if (userHandle == UserHandle.USER_SYSTEM) {
-                                mInjector.settingsGlobalPutStringForUser(
-                                        Settings.Global.ADB_ENABLED, "0", userHandle);
-                            }
-                        } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) {
-                            mInjector.settingsGlobalPutStringForUser(
-                                    Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
-                                    userHandle);
-                            mInjector.settingsGlobalPutStringForUser(
-                                    Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
-                                    userHandle);
-                        } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) {
-                            mInjector.settingsSecurePutIntForUser(
-                                    Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
-                                    userHandle);
-                        }
-                    }
-
-                    if (enabled != alreadyRestricted) {
-                        if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
-                            // Send out notifications however as some clients may want to reread the
-                            // value which actually changed due to a restriction having been
-                            // applied.
-                            final String property = Settings.Secure.SYS_PROP_SETTING_VERSION;
-                            long version = mInjector.systemPropertiesGetLong(property, 0) + 1;
-                            mInjector.systemPropertiesSet(property, Long.toString(version));
-
-                            final String name = Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
-                            Uri url = Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
-                            mContext.getContentResolver().notifyChange(url, null, true, userHandle);
-                        }
-                    }
-                    if (!enabled && alreadyRestricted) {
-                        if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
-                            mInjector.getIAudioService()
-                                    .setMicrophoneMute(false, mContext.getPackageName(),
-                                            userHandle);
-                        } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
-                            mInjector.getIAudioService()
-                                    .setMasterMute(false, 0, mContext.getPackageName(), userHandle);
-                        }
-                    }
-                } catch (RemoteException re) {
-                    Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
                 } finally {
                     mInjector.binderRestoreCallingIdentity(id);
                 }
@@ -6463,8 +6380,10 @@
                         deviceOwner == null ? null : deviceOwner.userRestrictions;
                 final Bundle profileOwnerRestrictions =
                         profileOwner == null ? null : profileOwner.userRestrictions;
+                final boolean cameraDisabled = getCameraDisabled(null, userId);
 
-                if (deviceOwnerRestrictions == null && profileOwnerRestrictions == null) {
+                if (deviceOwnerRestrictions == null && profileOwnerRestrictions == null
+                        && !cameraDisabled) {
                     // No restrictions to merge.
                     return inBundle;
                 }
@@ -6473,6 +6392,11 @@
                 UserRestrictionsUtils.merge(composed, deviceOwnerRestrictions);
                 UserRestrictionsUtils.merge(composed, profileOwnerRestrictions);
 
+                // Also merge in the camera restriction.
+                if (cameraDisabled) {
+                    composed.putBoolean(UserManager.DISALLOW_CAMERA, true);
+                }
+
                 return composed;
             }
         }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index eb7eb15..9e70138 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -46,11 +46,11 @@
     public void testWriteReadApplicationRestrictions() throws IOException {
         AtomicFile atomicFile = new AtomicFile(restrictionsFile);
         Bundle bundle = createBundle();
-        UserManagerService.writeApplicationRestrictionsLocked(bundle, atomicFile);
+        UserManagerService.writeApplicationRestrictionsLP(bundle, atomicFile);
         assertTrue(atomicFile.getBaseFile().exists());
         String s = FileUtils.readTextFile(restrictionsFile, 10000, "");
         System.out.println("restrictionsFile: " + s);
-        bundle = UserManagerService.readApplicationRestrictionsLocked(atomicFile);
+        bundle = UserManagerService.readApplicationRestrictionsLP(atomicFile);
         System.out.println("readApplicationRestrictionsLocked bundle: " + bundle);
         assertBundle(bundle);
     }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 13e600e..013154b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -385,9 +385,11 @@
                         timeNow);
                 final int packageCount = packages.size();
                 for (int p = 0; p < packageCount; p++) {
-                    final String packageName = packages.get(p).packageName;
-                    final boolean isIdle = isAppIdleFiltered(packageName, userId, service, timeNow,
-                            screenOnTime);
+                    final PackageInfo pi = packages.get(p);
+                    final String packageName = pi.packageName;
+                    final boolean isIdle = isAppIdleFiltered(packageName,
+                            UserHandle.getAppId(pi.applicationInfo.uid),
+                            userId, service, timeNow, screenOnTime);
                     mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
                             userId, isIdle ? 1 : 0, packageName));
                     mAppIdleHistory.addEntry(packageName, userId, isIdle, timeNow);
@@ -769,10 +771,17 @@
         if (mAppIdleParoled) {
             return false;
         }
-        return isAppIdleFiltered(packageName, userId, timeNow);
+        try {
+            ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(packageName,
+                    PackageManager.GET_UNINSTALLED_PACKAGES
+                            | PackageManager.GET_DISABLED_COMPONENTS);
+            return isAppIdleFiltered(packageName, ai.uid, userId, timeNow);
+        } catch (PackageManager.NameNotFoundException e) {
+        }
+        return false;
     }
 
-    boolean isAppIdleFiltered(String packageName, int userId, long timeNow) {
+    boolean isAppIdleFiltered(String packageName, int uidForAppId, int userId, long timeNow) {
         final UserUsageStatsService userService;
         final long screenOnTime;
         synchronized (mLock) {
@@ -782,7 +791,8 @@
             userService = getUserDataAndInitializeIfNeededLocked(userId, timeNow);
             screenOnTime = getScreenOnTimeLocked(timeNow);
         }
-        return isAppIdleFiltered(packageName, userId, userService, timeNow, screenOnTime);
+        return isAppIdleFiltered(packageName, UserHandle.getAppId(uidForAppId), userId,
+                userService, timeNow, screenOnTime);
     }
 
     /**
@@ -791,14 +801,22 @@
      * This happens if the device is plugged in or temporarily allowed to make exceptions.
      * Called by interface impls.
      */
-    private boolean isAppIdleFiltered(String packageName, int userId,
+    private boolean isAppIdleFiltered(String packageName, int appId, int userId,
             UserUsageStatsService userService, long timeNow, long screenOnTime) {
         if (packageName == null) return false;
         // If not enabled at all, of course nobody is ever idle.
         if (!mAppIdleEnabled) {
             return false;
         }
-        if (packageName.equals("android")) return false;
+        if (appId < Process.FIRST_APPLICATION_UID) {
+            // System uids never go idle.
+            return false;
+        }
+        if (packageName.equals("android")) {
+            // Nor does the framework (which should be redundant with the above, but for MR1 we will
+            // retain this for safety).
+            return false;
+        }
         try {
             // We allow all whitelisted apps, including those that don't want to be whitelisted
             // for idle mode, because app idle (aka app standby) is really not as big an issue
@@ -865,8 +883,8 @@
             ApplicationInfo ai = apps.get(i);
 
             // Check whether this app is idle.
-            boolean idle = isAppIdleFiltered(ai.packageName, userId, userService, timeNow,
-                    screenOnTime);
+            boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid),
+                    userId, userService, timeNow, screenOnTime);
 
             int index = uidStates.indexOfKey(ai.uid);
             if (index < 0) {
@@ -1352,8 +1370,8 @@
         }
 
         @Override
-        public boolean isAppIdle(String packageName, int userId) {
-            return UsageStatsService.this.isAppIdleFiltered(packageName, userId, -1);
+        public boolean isAppIdle(String packageName, int uidForAppId, int userId) {
+            return UsageStatsService.this.isAppIdleFiltered(packageName, uidForAppId, userId, -1);
         }
 
         @Override
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index e11c8d3..d1d6e0d 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -332,9 +332,24 @@
         return 0;
     }
 
+    /**
+     * @hide
+     */
+    public static String givePrintableIccid(String iccId) {
+        String iccIdToPrint = null;
+        if (iccId != null) {
+            if (iccId.length() > 9) {
+                iccIdToPrint = iccId.substring(0, 9) + "XXXXXXXXXXX";
+            } else {
+                iccIdToPrint = iccId;
+            }
+        }
+        return iccIdToPrint;
+    }
+
     @Override
     public String toString() {
-        String iccIdToPrint = mIccId != null ? mIccId.substring(0, 9) + "XXXXXXXXXXX" : null;
+        String iccIdToPrint = givePrintableIccid(mIccId);
         return "{id=" + mId + ", iccId=" + iccIdToPrint + " simSlotIndex=" + mSimSlotIndex
                 + " displayName=" + mDisplayName + " carrierName=" + mCarrierName
                 + " nameSource=" + mNameSource + " iconTint=" + mIconTint
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
index 861a379..5f84e0c 100644
--- a/telephony/java/com/android/ims/ImsCallProfile.java
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -188,6 +188,20 @@
     public static final String EXTRA_CODEC = "Codec";
     public static final String EXTRA_DISPLAY_TEXT = "DisplayText";
     public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
+
+    /**
+     * Extra key which the RIL can use to indicate the radio technology used for a call.
+     * Valid values are:
+     * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE},
+     * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_IWLAN}, and the other defined
+     * {@code RIL_RADIO_TECHNOLOGY_*} constants.
+     * Note: Despite the fact the {@link android.telephony.ServiceState} values are integer
+     * constants, the values passed for the {@link #EXTRA_CALL_RAT_TYPE} should be strings (e.g.
+     * "14" vs (int) 14).
+     * Note: This is used by {@link com.android.internal.telephony.imsphone.ImsPhoneConnection#
+     *      updateWifiStateFromExtras(Bundle)} to determine whether to set the
+     * {@link android.telecom.Connection#CAPABILITY_WIFI} capability on a connection.
+     */
     public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
 
     public int mServiceType;
diff --git a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java
index b4e0c70..4771b6c 100644
--- a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java
+++ b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java
@@ -24,6 +24,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManager.MemoryInfo;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
@@ -95,11 +96,9 @@
     private boolean mResumed;
 
     // Drop one frame per half second.
-    // TODO(khmel)
-    // Add a feature flag and set the target FPS dependent on the target system as e.g.:
-    // 59FPS for MULTI_WINDOW and 54 otherwise (to satisfy the default lax Android requirements).
     private double mRefreshRate;
     private double mTargetFPS;
+    private boolean mAndromeda;
 
     private int mWidth;
     private int mHeight;
@@ -182,6 +181,10 @@
         return score;
     }
 
+    public boolean isAndromeda() {
+        return mAndromeda;
+    }
+
     @Override
     public void onClick(View view) {
         if (view == mMeasureCompositionButton) {
@@ -247,6 +250,9 @@
 
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
 
+        // Detect Andromeda devices by having free-form window management feature.
+        mAndromeda = getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
         detectRefreshRate();
 
         // To layouts in parent. First contains list of Surfaces and second
@@ -513,7 +519,8 @@
         }
 
         MemoryInfo memInfo = getMemoryInfo();
-        String info = "Available " +
+        String platformName = mAndromeda ? "Andromeda" : "Android";
+        String info = platformName + ": available " +
                 getReadableMemory(memInfo.availMem) + " from " +
                 getReadableMemory(memInfo.totalMem) + ".\nVisible " +
                 visibleCnt + " from " + mViews.size() + " " +
diff --git a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
index 3f04888..388f91a 100644
--- a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
+++ b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
@@ -17,6 +17,7 @@
 
 import android.app.Activity;
 import android.graphics.PixelFormat;
+import android.os.Build;
 import android.os.Bundle;
 import android.surfacecomposition.SurfaceCompositionMeasuringActivity.AllocationScore;
 import android.surfacecomposition.SurfaceCompositionMeasuringActivity.CompositorScore;
@@ -44,11 +45,16 @@
         PixelFormat.OPAQUE,
     };
 
-    // Based on Nexus 9 performance which is usually < 9.0.
-    private final static double[] MIN_ACCEPTED_COMPOSITION_SCORE = new double[] {
+    // Nexus 9 performance is around 8.8. We distinguish results for Andromeda and
+    // Android devices. Andromeda devices require higher performance score.
+    private final static double[] MIN_ACCEPTED_COMPOSITION_SCORE_ANDROMDEDA = new double[] {
         8.0,
         8.0,
     };
+    private final static double[] MIN_ACCEPTED_COMPOSITION_SCORE_ANDROID = new double[] {
+        4.0,
+        4.0,
+    };
 
     // Based on Nexus 6 performance which is usually < 28.0.
     private final static double[] MIN_ACCEPTED_ALLOCATION_SCORE = new double[] {
@@ -66,6 +72,8 @@
     @SmallTest
     public void testSurfaceCompositionPerformance() {
         Bundle status = new Bundle();
+        double[] minScores = getActivity().isAndromeda() ?
+                MIN_ACCEPTED_COMPOSITION_SCORE_ANDROMDEDA : MIN_ACCEPTED_COMPOSITION_SCORE_ANDROID;
         for (int i = 0; i < TEST_PIXEL_FORMATS.length; ++i) {
             int pixelFormat = TEST_PIXEL_FORMATS[i];
             String formatName = SurfaceCompositionMeasuringActivity.getPixelFormatInfo(pixelFormat);
@@ -73,8 +81,8 @@
             Log.i(TAG, "testSurfaceCompositionPerformance(" + formatName + ") = " + score);
             assertTrue("Device does not support surface(" + formatName + ") composition " +
                     "performance score. " + score.mSurfaces + " < " +
-                    MIN_ACCEPTED_COMPOSITION_SCORE[i] + ".",
-                    score.mSurfaces >= MIN_ACCEPTED_COMPOSITION_SCORE[i]);
+                    minScores[i] + ". Build: " + Build.FINGERPRINT + ".",
+                    score.mSurfaces >= minScores[i]);
             // Send status only for TRANSLUCENT format.
             if (pixelFormat == PixelFormat.TRANSLUCENT) {
                 status.putDouble(KEY_SURFACE_COMPOSITION_PERFORMANCE, score.mSurfaces);
@@ -96,7 +104,8 @@
             Log.i(TAG, "testSurfaceAllocationPerformance(" + formatName + ") = " + score);
             assertTrue("Device does not support surface(" + formatName + ") allocation " +
                     "performance score. " + score.mMedian + " < " +
-                    MIN_ACCEPTED_ALLOCATION_SCORE[i] + ".",
+                    MIN_ACCEPTED_ALLOCATION_SCORE[i] + ". Build: " +
+                    Build.FINGERPRINT + ".",
                     score.mMedian >= MIN_ACCEPTED_ALLOCATION_SCORE[i]);
             // Send status only for TRANSLUCENT format.
             if (pixelFormat == PixelFormat.TRANSLUCENT) {
diff --git a/tools/aapt/ZipFile.cpp b/tools/aapt/ZipFile.cpp
index 36f4e73..2840826 100644
--- a/tools/aapt/ZipFile.cpp
+++ b/tools/aapt/ZipFile.cpp
@@ -364,7 +364,7 @@
     long lfhPosn, startPosn, endPosn, uncompressedLen;
     FILE* inputFp = NULL;
     unsigned long crc;
-    time_t modWhen;
+    time_t modWhen = 0;
 
     if (mReadOnly)
         return INVALID_OPERATION;
@@ -497,7 +497,6 @@
      */
     pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
         compressionMethod);
-    modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
     pEntry->setModWhen(modWhen);
     pEntry->setLFHOffset(lfhPosn);
     mEOCD.mNumEntries++;
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 275476c..ceed21e 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -32,6 +32,7 @@
 	flatten/TableFlattener.cpp \
 	flatten/XmlFlattener.cpp \
 	link/AutoVersioner.cpp \
+	link/ManifestFixer.cpp \
 	link/PrivateAttributeMover.cpp \
 	link/ReferenceLinker.cpp \
 	link/TableMerger.cpp \
@@ -45,9 +46,11 @@
 	ConfigDescription.cpp \
 	Debug.cpp \
 	Flags.cpp \
-	JavaClassGenerator.cpp \
+	java/AnnotationProcessor.cpp \
+	java/JavaClassGenerator.cpp \
+	java/ManifestClassGenerator.cpp \
+	java/ProguardRules.cpp \
 	Locale.cpp \
-	ProguardRules.cpp \
 	Resource.cpp \
 	ResourceParser.cpp \
 	ResourceTable.cpp \
@@ -65,6 +68,7 @@
 	flatten/TableFlattener_test.cpp \
 	flatten/XmlFlattener_test.cpp \
 	link/AutoVersioner_test.cpp \
+	link/ManifestFixer_test.cpp \
 	link/PrivateAttributeMover_test.cpp \
 	link/ReferenceLinker_test.cpp \
 	link/TableMerger_test.cpp \
@@ -76,7 +80,8 @@
 	util/StringPiece_test.cpp \
 	util/Util_test.cpp \
 	ConfigDescription_test.cpp \
-	JavaClassGenerator_test.cpp \
+	java/JavaClassGenerator_test.cpp \
+	java/ManifestClassGenerator_test.cpp \
 	Locale_test.cpp \
 	Resource_test.cpp \
 	ResourceParser_test.cpp \
@@ -110,7 +115,7 @@
 endif
 
 cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
-cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions
+cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions -fno-rtti
 
 # ==========================================================
 # Build the host static library: libaapt2
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
index 8120fa7..64353de 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -164,6 +164,26 @@
     return false;
 }
 
+static bool parseScreenRound(const char* name, ResTable_config* out) {
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->screenLayout2 =
+                (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
+                | ResTable_config::SCREENROUND_ANY;
+        return true;
+    } else if (strcmp(name, "round") == 0) {
+        if (out) out->screenLayout2 =
+                (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
+                | ResTable_config::SCREENROUND_YES;
+        return true;
+    } else if (strcmp(name, "notround") == 0) {
+        if (out) out->screenLayout2 =
+                (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
+                | ResTable_config::SCREENROUND_NO;
+        return true;
+    }
+    return false;
+}
+
 static bool parseOrientation(const char* name, ResTable_config* out) {
     if (strcmp(name, kWildcardName) == 0) {
         if (out) out->orientation = out->ORIENTATION_ANY;
@@ -635,6 +655,13 @@
         }
     }
 
+    if (parseScreenRound(partIter->c_str(), &config)) {
+        ++partIter;
+        if (partIter == partsEnd) {
+            goto success;
+        }
+    }
+
     if (parseOrientation(partIter->c_str(), &config)) {
         ++partIter;
         if (partIter == partsEnd) {
@@ -725,7 +752,9 @@
 
 void ConfigDescription::applyVersionForCompatibility(ConfigDescription* config) {
     uint16_t minSdk = 0;
-    if (config->density == ResTable_config::DENSITY_ANY) {
+    if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
+        minSdk = SDK_MARSHMALLOW;
+    } else if (config->density == ResTable_config::DENSITY_ANY) {
         minSdk = SDK_LOLLIPOP;
     } else if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY
             || config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY
diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp
index 8370816..e68d6be 100644
--- a/tools/aapt2/ConfigDescription_test.cpp
+++ b/tools/aapt2/ConfigDescription_test.cpp
@@ -15,6 +15,8 @@
  */
 
 #include "ConfigDescription.h"
+#include "SdkConstants.h"
+
 #include "util/StringPiece.h"
 
 #include <gtest/gtest.h>
@@ -79,4 +81,19 @@
     EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode);
 }
 
+TEST(ConfigDescriptionTest, TestParsingRoundQualifier) {
+    ConfigDescription config;
+    EXPECT_TRUE(TestParse("round", &config));
+    EXPECT_EQ(android::ResTable_config::SCREENROUND_YES,
+    config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
+    EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
+    EXPECT_EQ(std::string("round-v23"), config.toString().string());
+
+    EXPECT_TRUE(TestParse("notround", &config));
+    EXPECT_EQ(android::ResTable_config::SCREENROUND_NO,
+              config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
+    EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
+    EXPECT_EQ(std::string("notround-v23"), config.toString().string());
+}
+
 } // namespace aapt
diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp
index 6ae5af7..9435396 100644
--- a/tools/aapt2/Flags.cpp
+++ b/tools/aapt2/Flags.cpp
@@ -16,6 +16,7 @@
 
 #include "Flags.h"
 #include "util/StringPiece.h"
+#include "util/Util.h"
 
 #include <iomanip>
 #include <iostream>
@@ -94,7 +95,14 @@
         if (flag.numArgs > 0) {
             argLine += " arg";
         }
-        *out << " " << std::setw(30) << std::left << argLine << flag.description << "\n";
+
+        // Split the description by newlines and write out the argument (which is empty after
+        // the first line) followed by the description line. This will make sure that multiline
+        // descriptions are still right justified and aligned.
+        for (StringPiece line : util::tokenize<char>(flag.description, '\n')) {
+            *out << " " << std::setw(30) << std::left << argLine << line << "\n";
+            argLine = " ";
+        }
     }
     *out << " " << std::setw(30) << std::left << "-h" << "Displays this help menu\n";
     out->flush();
diff --git a/tools/aapt2/ManifestValidator.cpp b/tools/aapt2/ManifestValidator.cpp
deleted file mode 100644
index 9f971fb..0000000
--- a/tools/aapt2/ManifestValidator.cpp
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * 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 "Logger.h"
-#include "ManifestValidator.h"
-#include "util/Maybe.h"
-#include "Source.h"
-#include "util/Util.h"
-
-#include <androidfw/ResourceTypes.h>
-
-namespace aapt {
-
-ManifestValidator::ManifestValidator(const android::ResTable& table)
-: mTable(table) {
-}
-
-bool ManifestValidator::validate(const Source& source, android::ResXMLParser* parser) {
-    SourceLogger logger(source);
-
-    android::ResXMLParser::event_code_t code;
-    while ((code = parser->next()) != android::ResXMLParser::END_DOCUMENT &&
-            code != android::ResXMLParser::BAD_DOCUMENT) {
-        if (code != android::ResXMLParser::START_TAG) {
-            continue;
-        }
-
-        size_t len = 0;
-        const StringPiece16 namespaceUri(parser->getElementNamespace(&len), len);
-        if (!namespaceUri.empty()) {
-            continue;
-        }
-
-        const StringPiece16 name(parser->getElementName(&len), len);
-        if (name.empty()) {
-            logger.error(parser->getLineNumber())
-                    << "failed to get the element name."
-                    << std::endl;
-            return false;
-        }
-
-        if (name == u"manifest") {
-            if (!validateManifest(source, parser)) {
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
-Maybe<StringPiece16> ManifestValidator::getAttributeValue(android::ResXMLParser* parser,
-                                                          size_t idx) {
-    android::Res_value value;
-    if (parser->getAttributeValue(idx, &value) < 0) {
-        return StringPiece16();
-    }
-
-    const android::ResStringPool* pool = &parser->getStrings();
-    if (value.dataType == android::Res_value::TYPE_REFERENCE) {
-        ssize_t strIdx = mTable.resolveReference(&value, 0x10000000u);
-        if (strIdx < 0) {
-            return {};
-        }
-        pool = mTable.getTableStringBlock(strIdx);
-    }
-
-    if (value.dataType != android::Res_value::TYPE_STRING || !pool) {
-        return {};
-    }
-    return util::getString(*pool, value.data);
-}
-
-Maybe<StringPiece16> ManifestValidator::getAttributeInlineValue(android::ResXMLParser* parser,
-                                                                size_t idx) {
-    android::Res_value value;
-    if (parser->getAttributeValue(idx, &value) < 0) {
-        return StringPiece16();
-    }
-
-    if (value.dataType != android::Res_value::TYPE_STRING) {
-        return {};
-    }
-    return util::getString(parser->getStrings(), value.data);
-}
-
-bool ManifestValidator::validateInlineAttribute(android::ResXMLParser* parser, size_t idx,
-                                                SourceLogger& logger,
-                                                const StringPiece16& charSet) {
-    size_t len = 0;
-    StringPiece16 element(parser->getElementName(&len), len);
-    StringPiece16 attributeName(parser->getAttributeName(idx, &len), len);
-    Maybe<StringPiece16> result = getAttributeInlineValue(parser, idx);
-    if (!result) {
-        logger.error(parser->getLineNumber())
-                << "<"
-                << element
-                << "> must have a '"
-                << attributeName
-                << "' attribute with a string literal value."
-                << std::endl;
-        return false;
-    }
-    return validateAttributeImpl(element, attributeName, result.value(), charSet,
-                                 parser->getLineNumber(), logger);
-}
-
-bool ManifestValidator::validateAttribute(android::ResXMLParser* parser, size_t idx,
-                                          SourceLogger& logger, const StringPiece16& charSet) {
-    size_t len = 0;
-    StringPiece16 element(parser->getElementName(&len), len);
-    StringPiece16 attributeName(parser->getAttributeName(idx, &len), len);
-    Maybe<StringPiece16> result = getAttributeValue(parser, idx);
-    if (!result) {
-        logger.error(parser->getLineNumber())
-                << "<"
-                << element
-                << "> must have a '"
-                << attributeName
-                << "' attribute that points to a string."
-                << std::endl;
-        return false;
-    }
-    return validateAttributeImpl(element, attributeName, result.value(), charSet,
-                                 parser->getLineNumber(), logger);
-}
-
-bool ManifestValidator::validateAttributeImpl(const StringPiece16& element,
-                                              const StringPiece16& attributeName,
-                                              const StringPiece16& attributeValue,
-                                              const StringPiece16& charSet, size_t lineNumber,
-                                              SourceLogger& logger) {
-    StringPiece16::const_iterator badIter =
-            util::findNonAlphaNumericAndNotInSet(attributeValue, charSet);
-    if (badIter != attributeValue.end()) {
-        logger.error(lineNumber)
-                << "tag <"
-                << element
-                << "> attribute '"
-                << attributeName
-                << "' has invalid character '"
-                << StringPiece16(badIter, 1)
-                << "'."
-                << std::endl;
-        return false;
-    }
-
-    if (!attributeValue.empty()) {
-        StringPiece16 trimmed = util::trimWhitespace(attributeValue);
-        if (attributeValue.begin() != trimmed.begin()) {
-            logger.error(lineNumber)
-                    << "tag <"
-                    << element
-                    << "> attribute '"
-                    << attributeName
-                    << "' can not start with whitespace."
-                    << std::endl;
-            return false;
-        }
-
-        if (attributeValue.end() != trimmed.end()) {
-            logger.error(lineNumber)
-                    << "tag <"
-                    << element
-                    << "> attribute '"
-                    << attributeName
-                    << "' can not end with whitespace."
-                    << std::endl;
-            return false;
-        }
-    }
-    return true;
-}
-
-constexpr const char16_t* kPackageIdentSet = u"._";
-
-bool ManifestValidator::validateManifest(const Source& source, android::ResXMLParser* parser) {
-    bool error = false;
-    SourceLogger logger(source);
-
-    const StringPiece16 kAndroid = u"android";
-    const StringPiece16 kPackage = u"package";
-    const StringPiece16 kSharedUserId = u"sharedUserId";
-
-    ssize_t idx;
-
-    idx = parser->indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
-    if (idx < 0) {
-        logger.error(parser->getLineNumber())
-                << "missing package attribute."
-                << std::endl;
-        error = true;
-    } else {
-        error |= !validateInlineAttribute(parser, idx, logger, kPackageIdentSet);
-    }
-
-    idx = parser->indexOfAttribute(kAndroid.data(), kAndroid.size(),
-                                   kSharedUserId.data(), kSharedUserId.size());
-    if (idx >= 0) {
-        error |= !validateInlineAttribute(parser, idx, logger, kPackageIdentSet);
-    }
-    return !error;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestValidator.h b/tools/aapt2/ManifestValidator.h
deleted file mode 100644
index 1a7f48e..0000000
--- a/tools/aapt2/ManifestValidator.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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 AAPT_MANIFEST_VALIDATOR_H
-#define AAPT_MANIFEST_VALIDATOR_H
-
-#include "Logger.h"
-#include "util/Maybe.h"
-#include "Source.h"
-#include "util/StringPiece.h"
-
-#include <androidfw/ResourceTypes.h>
-
-namespace aapt {
-
-class ManifestValidator {
-public:
-    ManifestValidator(const android::ResTable& table);
-    ManifestValidator(const ManifestValidator&) = delete;
-
-    bool validate(const Source& source, android::ResXMLParser* parser);
-
-private:
-    bool validateManifest(const Source& source, android::ResXMLParser* parser);
-
-    Maybe<StringPiece16> getAttributeInlineValue(android::ResXMLParser* parser, size_t idx);
-    Maybe<StringPiece16> getAttributeValue(android::ResXMLParser* parser, size_t idx);
-
-    bool validateInlineAttribute(android::ResXMLParser* parser, size_t idx,
-                                 SourceLogger& logger, const StringPiece16& charSet);
-    bool validateAttribute(android::ResXMLParser* parser, size_t idx, SourceLogger& logger,
-                           const StringPiece16& charSet);
-    bool validateAttributeImpl(const StringPiece16& element, const StringPiece16& attributeName,
-                               const StringPiece16& attributeValue, const StringPiece16& charSet,
-                               size_t lineNumber, SourceLogger& logger);
-
-    const android::ResTable& mTable;
-};
-
-} // namespace aapt
-
-#endif // AAPT_MANIFEST_VALIDATOR_H
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index 1962f58..34dc1d5 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -36,7 +36,6 @@
         case ResourceType::kFraction:      return u"fraction";
         case ResourceType::kId:            return u"id";
         case ResourceType::kInteger:       return u"integer";
-        case ResourceType::kIntegerArray:  return u"integer-array";
         case ResourceType::kInterpolator:  return u"interpolator";
         case ResourceType::kLayout:        return u"layout";
         case ResourceType::kMenu:          return u"menu";
@@ -65,7 +64,6 @@
         { u"fraction", ResourceType::kFraction },
         { u"id", ResourceType::kId },
         { u"integer", ResourceType::kInteger },
-        { u"integer-array", ResourceType::kIntegerArray },
         { u"interpolator", ResourceType::kInterpolator },
         { u"layout", ResourceType::kLayout },
         { u"menu", ResourceType::kMenu },
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 7ef1897..a7afbb5 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -47,7 +47,6 @@
     kFraction,
     kId,
     kInteger,
-    kIntegerArray,
     kInterpolator,
     kLayout,
     kMenu,
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 44710eb..2d6c0c2 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -62,6 +62,7 @@
                                        StyleString* outStyleString) {
     std::vector<Span> spanStack;
 
+    bool error = false;
     outRawString->clear();
     outStyleString->spans.clear();
     util::StringBuilder builder;
@@ -84,7 +85,6 @@
             spanStack.pop_back();
 
         } else if (event == XmlPullParser::Event::kText) {
-            // TODO(adamlesinski): Verify format strings.
             outRawString->append(parser->getText());
             builder.append(parser->getText());
 
@@ -116,9 +116,10 @@
             if (builder.str().size() > std::numeric_limits<uint32_t>::max()) {
                 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
                              << "style string '" << builder.str() << "' is too long");
-                return false;
+                error = true;
+            } else {
+                spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
             }
-            spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
 
         } else if (event == XmlPullParser::Event::kComment) {
             // Skip
@@ -129,7 +130,7 @@
     assert(spanStack.empty() && "spans haven't been fully processed");
 
     outStyleString->str = builder.str();
-    return true;
+    return !error;
 }
 
 bool ResourceParser::parse(XmlPullParser* parser) {
@@ -308,6 +309,9 @@
         } else if (elementName == u"dimen") {
             parsedResource.name.type = ResourceType::kDimen;
             result = parsePrimitive(parser, &parsedResource);
+        } else if (elementName == u"fraction") {
+            parsedResource.name.type = ResourceType::kFraction;
+            result = parsePrimitive(parser, &parsedResource);
         } else if (elementName == u"style") {
             parsedResource.name.type = ResourceType::kStyle;
             result = parseStyle(parser, &parsedResource);
@@ -321,7 +325,7 @@
             parsedResource.name.type = ResourceType::kArray;
             result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_STRING);
         } else if (elementName == u"integer-array") {
-            parsedResource.name.type = ResourceType::kIntegerArray;
+            parsedResource.name.type = ResourceType::kArray;
             result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_INTEGER);
         } else if (elementName == u"declare-styleable") {
             parsedResource.name.type = ResourceType::kStyleable;
@@ -434,13 +438,39 @@
 bool ResourceParser::parseString(XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
-    // TODO(adamlesinski): Read "untranslateable" attribute.
+    bool formatted = true;
+    if (Maybe<StringPiece16> formattedAttr = findAttribute(parser, u"formatted")) {
+        if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) {
+            mDiag->error(DiagMessage(source) << "invalid value for 'formatted'. Must be a boolean");
+            return false;
+        }
+    }
+
+    bool untranslateable = false;
+    if (Maybe<StringPiece16> untranslateableAttr = findAttribute(parser, u"untranslateable")) {
+        if (!ResourceUtils::tryParseBool(untranslateableAttr.value(), &untranslateable)) {
+            mDiag->error(DiagMessage(source)
+                         << "invalid value for 'untranslateable'. Must be a boolean");
+            return false;
+        }
+    }
 
     outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
     if (!outResource->value) {
         mDiag->error(DiagMessage(source) << "not a valid string");
         return false;
     }
+
+    if (formatted || untranslateable) {
+        if (String* stringValue = valueCast<String>(outResource->value.get())) {
+            if (!util::verifyJavaStringFormat(*stringValue->value)) {
+                mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                             << "multiple substitutions specified in non-positional format; "
+                                "did you mean to add the formatted=\"false\" attribute?");
+                return false;
+            }
+        }
+    }
     return true;
 }
 
@@ -464,6 +494,8 @@
         typeMask |= android::ResTable_map::TYPE_INTEGER;
         break;
 
+    case ResourceType::kFraction:
+        // fallthrough
     case ResourceType::kDimen:
         typeMask |= android::ResTable_map::TYPE_DIMENSION
                   | android::ResTable_map::TYPE_FLOAT
@@ -576,6 +608,12 @@
     return mask;
 }
 
+/**
+ * Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
+ */
+static bool shouldIgnoreElement(const StringPiece16& ns, const StringPiece16& name) {
+    return ns.empty() && (name == u"skip" || name == u"eat-comment");
+}
 
 bool ResourceParser::parseAttr(XmlPullParser* parser, ParsedResource* outResource) {
     outResource->source = mSource.withLine(parser->getLineNumber());
@@ -613,25 +651,30 @@
     bool error = false;
     const size_t depth = parser->getDepth();
     while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
-            // Skip comments and text.
+        if (parser->getEvent() == XmlPullParser::Event::kComment) {
+            comment = util::trimWhitespace(parser->getComment()).toString();
+            continue;
+        } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            // Skip text.
             continue;
         }
 
+        const Source itemSource = mSource.withLine(parser->getLineNumber());
         const std::u16string& elementNamespace = parser->getElementNamespace();
         const std::u16string& elementName = parser->getElementName();
-        if (elementNamespace == u"" && (elementName == u"flag" || elementName == u"enum")) {
+        if (elementNamespace.empty() && (elementName == u"flag" || elementName == u"enum")) {
             if (elementName == u"enum") {
                 if (typeMask & android::ResTable_map::TYPE_FLAGS) {
-                    mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                    mDiag->error(DiagMessage(itemSource)
                                  << "can not define an <enum>; already defined a <flag>");
                     error = true;
                     continue;
                 }
                 typeMask |= android::ResTable_map::TYPE_ENUM;
+
             } else if (elementName == u"flag") {
                 if (typeMask & android::ResTable_map::TYPE_ENUM) {
-                    mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                    mDiag->error(DiagMessage(itemSource)
                                  << "can not define a <flag>; already defined an <enum>");
                     error = true;
                     continue;
@@ -642,21 +685,22 @@
             if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) {
                 ParsedResource childResource;
                 childResource.name = s.value().symbol.name.value();
-                childResource.source = mSource.withLine(parser->getLineNumber());
+                childResource.source = itemSource;
                 childResource.value = util::make_unique<Id>();
                 outResource->childResources.push_back(std::move(childResource));
+
+                s.value().symbol.setComment(std::move(comment));
+                s.value().symbol.setSource(itemSource);
                 items.push_back(std::move(s.value()));
             } else {
                 error = true;
             }
-        } else if (elementName == u"skip" || elementName == u"eat-comment") {
-            comment = u"";
-
-        } else {
-            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
-                         << ":" << elementName << ">");
+        } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+            mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
             error = true;
         }
+
+        comment = {};
     }
 
     if (error) {
@@ -716,11 +760,10 @@
         p++;
     }
 
-    return ResourceName{ package.toString(), ResourceType::kAttr,
-        name.empty() ? str.toString() : name.toString() };
+    return ResourceName(package.toString(), ResourceType::kAttr,
+                        name.empty() ? str.toString() : name.toString());
 }
 
-
 bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
@@ -783,7 +826,6 @@
     }
 
     bool error = false;
-    std::u16string comment;
     const size_t depth = parser->getDepth();
     while (XmlPullParser::nextChildNode(parser, depth)) {
         if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
@@ -796,11 +838,7 @@
         if (elementNamespace == u"" && elementName == u"item") {
             error |= !parseStyleItem(parser, style.get());
 
-        } else if (elementNamespace.empty() &&
-                (elementName == u"skip" || elementName == u"eat-comment")) {
-            comment = u"";
-
-        } else {
+        } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
             mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
                          << ":" << elementName << ">");
             error = true;
@@ -820,7 +858,6 @@
     const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Array> array = util::make_unique<Array>();
 
-    std::u16string comment;
     bool error = false;
     const size_t depth = parser->getDepth();
     while (XmlPullParser::nextChildNode(parser, depth)) {
@@ -839,13 +876,10 @@
                 error = true;
                 continue;
             }
+            item->setSource(itemSource);
             array->items.emplace_back(std::move(item));
 
-        } else if (elementNamespace.empty() &&
-                (elementName == u"skip" || elementName == u"eat-comment")) {
-            comment = u"";
-
-        } else {
+        } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
             mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
                          << "unknown tag <" << elementNamespace << ":" << elementName << ">");
             error = true;
@@ -864,7 +898,6 @@
     const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Plural> plural = util::make_unique<Plural>();
 
-    std::u16string comment;
     bool error = false;
     const size_t depth = parser->getDepth();
     while (XmlPullParser::nextChildNode(parser, depth)) {
@@ -873,13 +906,14 @@
             continue;
         }
 
+        const Source itemSource = mSource.withLine(parser->getLineNumber());
         const std::u16string& elementNamespace = parser->getElementNamespace();
         const std::u16string& elementName = parser->getElementName();
         if (elementNamespace.empty() && elementName == u"item") {
             const auto endAttrIter = parser->endAttributes();
             auto attrIter = parser->findAttribute(u"", u"quantity");
             if (attrIter == endAttrIter || attrIter->value.empty()) {
-                mDiag->error(DiagMessage(source) << "<item> in <plurals> requires attribute "
+                mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute "
                              << "'quantity'");
                 error = true;
                 continue;
@@ -900,7 +934,7 @@
             } else if (trimmedQuantity == u"other") {
                 index = Plural::Other;
             } else {
-                mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                mDiag->error(DiagMessage(itemSource)
                              << "<item> in <plural> has invalid value '" << trimmedQuantity
                              << "' for attribute 'quantity'");
                 error = true;
@@ -908,7 +942,7 @@
             }
 
             if (plural->values[index]) {
-                mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                mDiag->error(DiagMessage(itemSource)
                              << "duplicate quantity '" << trimmedQuantity << "'");
                 error = true;
                 continue;
@@ -918,11 +952,10 @@
                                                    kNoRawString))) {
                 error = true;
             }
-        } else if (elementNamespace.empty() &&
-                (elementName == u"skip" || elementName == u"eat-comment")) {
-            comment = u"";
-        } else {
-            mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":"
+            plural->values[index]->setSource(itemSource);
+
+        } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+            mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
                          << elementName << ">");
             error = true;
         }
@@ -944,43 +977,52 @@
     bool error = false;
     const size_t depth = parser->getDepth();
     while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
-            // Ignore text and comments.
+        if (parser->getEvent() == XmlPullParser::Event::kComment) {
+            comment = util::trimWhitespace(parser->getComment()).toString();
+            continue;
+        } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+            // Ignore text.
             continue;
         }
 
+        const Source itemSource = mSource.withLine(parser->getLineNumber());
         const std::u16string& elementNamespace = parser->getElementNamespace();
         const std::u16string& elementName = parser->getElementName();
         if (elementNamespace.empty() && elementName == u"attr") {
             const auto endAttrIter = parser->endAttributes();
             auto attrIter = parser->findAttribute(u"", u"name");
             if (attrIter == endAttrIter || attrIter->value.empty()) {
-                mDiag->error(DiagMessage(source) << "<attr> tag must have a 'name' attribute");
+                mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute");
                 error = true;
                 continue;
             }
 
+            // Create the ParsedResource that will add the attribute to the table.
             ParsedResource childResource;
             childResource.name = ResourceName({}, ResourceType::kAttr, attrIter->value);
-            childResource.source = mSource.withLine(parser->getLineNumber());
+            childResource.source = itemSource;
+            childResource.comment = std::move(comment);
 
             if (!parseAttrImpl(parser, &childResource, true)) {
                 error = true;
                 continue;
             }
 
-            styleable->entries.push_back(Reference(childResource.name));
+            // Create the reference to this attribute.
+            Reference childRef(childResource.name);
+            childRef.setComment(childResource.comment);
+            childRef.setSource(itemSource);
+            styleable->entries.push_back(std::move(childRef));
+
             outResource->childResources.push_back(std::move(childResource));
 
-        } else if (elementNamespace.empty() &&
-                (elementName == u"skip" || elementName == u"eat-comment")) {
-            comment = u"";
-
-        } else {
-            mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":"
+        } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+            mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
                          << elementName << ">");
             error = true;
         }
+
+        comment = {};
     }
 
     if (error) {
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index af6bf67..2f5daae 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -414,6 +414,34 @@
     EXPECT_EQ(value->getComment(), u"One");
 }
 
+TEST_F(ResourceParserTest, ParseNestedComments) {
+    // We only care about declare-styleable and enum/flag attributes because comments
+    // from those end up in R.java
+    std::string input = R"EOF(
+        <declare-styleable name="foo">
+          <!-- The name of the bar -->
+          <attr name="barName" format="string|reference" />
+        </declare-styleable>
+
+        <attr name="foo">
+          <!-- The very first -->
+          <enum name="one" value="1" />
+        </attr>)EOF";
+    ASSERT_TRUE(testParse(input));
+
+    Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
+    ASSERT_NE(nullptr, styleable);
+    ASSERT_EQ(1u, styleable->entries.size());
+
+    EXPECT_EQ(StringPiece16(u"The name of the bar"), styleable->entries.front().getComment());
+
+    Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
+    ASSERT_NE(nullptr, attr);
+    ASSERT_EQ(1u, attr->symbols.size());
+
+    EXPECT_EQ(StringPiece16(u"The very first"), attr->symbols.front().symbol.getComment());
+}
+
 /*
  * Declaring an ID as public should not require a separate definition
  * (as an ID has no value).
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 0db1c37..d3c3c10 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -74,9 +74,11 @@
             return false;
         }
 
-        outRef->package = package;
-        outRef->type = *parsedType;
-        outRef->entry = entry;
+        if (outRef != nullptr) {
+            outRef->package = package;
+            outRef->type = *parsedType;
+            outRef->entry = entry;
+        }
         if (outCreate) {
             *outCreate = create;
         }
@@ -88,6 +90,10 @@
     return false;
 }
 
+bool isReference(const StringPiece16& str) {
+    return tryParseReference(str, nullptr, nullptr, nullptr);
+}
+
 bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) {
     StringPiece16 trimmedStr(util::trimWhitespace(str));
     if (trimmedStr.empty()) {
@@ -322,18 +328,36 @@
     return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
 }
 
-std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) {
+bool tryParseBool(const StringPiece16& str, bool* outValue) {
     StringPiece16 trimmedStr(util::trimWhitespace(str));
-    uint32_t data = 0;
     if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
-        data = 0xffffffffu;
-    } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") {
-        return {};
+        if (outValue) {
+            *outValue = true;
+        }
+        return true;
+    } else if (trimmedStr == u"false" || trimmedStr == u"FALSE") {
+        if (outValue) {
+            *outValue = false;
+        }
+        return true;
     }
-    android::Res_value value = { };
-    value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
-    value.data = data;
-    return util::make_unique<BinaryPrimitive>(value);
+    return false;
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) {
+    bool result = false;
+    if (tryParseBool(str, &result)) {
+        android::Res_value value = {};
+        value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
+
+        if (result) {
+            value.data = 0xffffffffu;
+        } else {
+            value.data = 0;
+        }
+        return util::make_unique<BinaryPrimitive>(value);
+    }
+    return {};
 }
 
 std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) {
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 118a2ee..851edc8 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -49,11 +49,21 @@
                        bool* outCreate = nullptr, bool* outPrivate = nullptr);
 
 /*
+ * Returns true if the string is in the form of a resource reference (@[+][package:]type/name).
+ */
+bool isReference(const StringPiece16& str);
+
+/*
  * Returns true if the string was parsed as an attribute reference (?[package:]type/name),
  * with `outReference` set to the parsed reference.
  */
 bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference);
 
+/**
+ * Returns true if the value is a boolean, putting the result in `outValue`.
+ */
+bool tryParseBool(const StringPiece16& str, bool* outValue);
+
 /*
  * Returns a Reference, or None Maybe instance if the string `str` was parsed as a
  * valid reference to a style.
diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp
index d957999..48dc521 100644
--- a/tools/aapt2/Resource_test.cpp
+++ b/tools/aapt2/Resource_test.cpp
@@ -69,10 +69,6 @@
     ASSERT_NE(type, nullptr);
     EXPECT_EQ(*type, ResourceType::kInteger);
 
-    type = parseResourceType(u"integer-array");
-    ASSERT_NE(type, nullptr);
-    EXPECT_EQ(*type, ResourceType::kIntegerArray);
-
     type = parseResourceType(u"interpolator");
     ASSERT_NE(type, nullptr);
     EXPECT_EQ(*type, ResourceType::kInterpolator);
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 803da03..282ed9a 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -42,6 +42,7 @@
     SDK_KITKAT_WATCH = 20,
     SDK_LOLLIPOP = 21,
     SDK_LOLLIPOP_MR1 = 22,
+    SDK_MARSHMALLOW = 23,
 };
 
 size_t findAttributeSdkLevel(ResourceId id);
diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/XmlDom.cpp
index d948775..b769c76 100644
--- a/tools/aapt2/XmlDom.cpp
+++ b/tools/aapt2/XmlDom.cpp
@@ -125,7 +125,7 @@
     Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
 
     assert(!stack->nodeStack.empty());
-    stack->nodeStack.top()->comment = std::move(stack->pendingComment);
+    //stack->nodeStack.top()->comment = std::move(stack->pendingComment);
     stack->nodeStack.pop();
 }
 
@@ -194,7 +194,7 @@
 
     XML_ParserFree(parser);
     if (stack.root) {
-        return util::make_unique<XmlResource>(ResourceFile{}, std::move(stack.root));
+        return util::make_unique<XmlResource>(ResourceFile{ {}, {}, source }, std::move(stack.root));
     }
     return {};
 }
@@ -317,6 +317,22 @@
     return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
 }
 
+Element* findRootElement(Node* node) {
+    if (!node) {
+        return nullptr;
+    }
+
+    Element* el = nullptr;
+    while ((el = nodeCast<Element>(node)) == nullptr) {
+        if (node->children.empty()) {
+            return nullptr;
+        }
+        // We are looking for the first element, and namespaces can only have one child.
+        node = node->children.front().get();
+    }
+    return el;
+}
+
 void Node::addChild(std::unique_ptr<Node> child) {
     child->parent = this;
     children.push_back(std::move(child));
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h
index c095f08..9a46bcb 100644
--- a/tools/aapt2/XmlDom.h
+++ b/tools/aapt2/XmlDom.h
@@ -34,6 +34,8 @@
 namespace aapt {
 namespace xml {
 
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
 struct RawVisitor;
 
 /**
@@ -132,6 +134,8 @@
 std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
                                      const Source& source);
 
+Element* findRootElement(Node* node);
+
 /**
  * A visitor interface for the different XML Node subtypes. This will not traverse into
  * children. Use Visitor for that.
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index 38cda09..c1ff556 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -132,6 +132,32 @@
     uint32_t count;
 };
 
+/**
+ * A structure representing source data for a resource entry.
+ * Appears after an android::ResTable_entry or android::ResTable_map_entry.
+ *
+ * TODO(adamlesinski): This causes some issues when runtime code checks
+ * the size of an android::ResTable_entry. It assumes it is an
+ * android::ResTable_map_entry if the size is bigger than an android::ResTable_entry
+ * which may not be true if this structure is present.
+ */
+struct ResTable_entry_source {
+    /**
+     * File path reference.
+     */
+    android::ResStringPool_ref path;
+
+    /**
+     * Line number this resource was defined on.
+     */
+    uint32_t line;
+
+    /**
+     * Comment string reference.
+     */
+    android::ResStringPool_ref comment;
+};
+
 struct Public_entry {
     uint16_t entryId;
 
@@ -143,8 +169,7 @@
 
     uint16_t state;
     android::ResStringPool_ref key;
-    android::ResStringPool_ref source;
-    uint32_t sourceLine;
+    ResTable_entry_source source;
 };
 
 /**
@@ -173,28 +198,7 @@
      * The index into the string pool where the name of this
      * symbol exists.
      */
-    uint32_t stringIndex;
-};
-
-/**
- * A structure representing the source of a resourc entry.
- * Appears after an android::ResTable_entry or android::ResTable_map_entry.
- *
- * TODO(adamlesinski): This causes some issues when runtime code checks
- * the size of an android::ResTable_entry. It assumes it is an
- * android::ResTable_map_entry if the size is bigger than an android::ResTable_entry
- * which may not be true if this structure is present.
- */
-struct ResTable_entry_source {
-    /**
-     * Index into the source string pool.
-     */
-    uint32_t pathIndex;
-
-    /**
-     * Line number this resource was defined on.
-     */
-    uint32_t line;
+    android::ResStringPool_ref name;
 };
 
 /**
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 47fa2a6..6b90fb2 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -54,9 +54,16 @@
 struct FlatEntry {
     ResourceEntry* entry;
     Value* value;
+
+    // The entry string pool index to the entry's name.
     uint32_t entryKey;
+
+    // The source string pool index to the source file path.
     uint32_t sourcePathKey;
     uint32_t sourceLine;
+
+    // The source string pool index to the comment.
+    uint32_t commentKey;
 };
 
 class SymbolWriter {
@@ -318,8 +325,9 @@
         if (mOptions.useExtendedChunks) {
             // Write the extra source block. This will be ignored by the Android runtime.
             ResTable_entry_source* sourceBlock = buffer->nextBlock<ResTable_entry_source>();
-            sourceBlock->pathIndex = util::hostToDevice32(entry->sourcePathKey);
+            sourceBlock->path.index = util::hostToDevice32(entry->sourcePathKey);
             sourceBlock->line = util::hostToDevice32(entry->sourceLine);
+            sourceBlock->comment.index = util::hostToDevice32(entry->commentKey);
             outEntry->size += sizeof(*sourceBlock);
         }
 
@@ -486,12 +494,14 @@
                 publicEntry->entryId = util::hostToDevice32(entry->id.value());
                 publicEntry->key.index = util::hostToDevice32(mKeyPool.makeRef(
                         entry->name).getIndex());
-                publicEntry->source.index = util::hostToDevice32(mSourcePool->makeRef(
+                publicEntry->source.path.index = util::hostToDevice32(mSourcePool->makeRef(
                         util::utf8ToUtf16(entry->symbolStatus.source.path)).getIndex());
                 if (entry->symbolStatus.source.line) {
-                    publicEntry->sourceLine = util::hostToDevice32(
+                    publicEntry->source.line = util::hostToDevice32(
                             entry->symbolStatus.source.line.value());
                 }
+                publicEntry->source.comment.index = util::hostToDevice32(mSourcePool->makeRef(
+                        entry->symbolStatus.comment).getIndex());
 
                 switch (entry->symbolStatus.state) {
                 case SymbolState::kPrivate:
@@ -565,13 +575,16 @@
                         lineNumber = value->getSource().line.value();
                     }
 
+                    const StringPool::Ref commentRef = mSourcePool->makeRef(value->getComment());
+
                     configToEntryListMap[configValue.config]
                             .push_back(FlatEntry{
                                     entry,
                                     value,
                                     keyIndex,
                                     (uint32_t) sourceRef.getIndex(),
-                                    lineNumber });
+                                    lineNumber,
+                                    (uint32_t) commentRef.getIndex() });
                 }
             }
 
@@ -680,7 +693,7 @@
     // Update the offsets to their final values.
     if (symbolEntryData) {
         for (SymbolWriter::Entry& entry : symbolOffsets) {
-            symbolEntryData->stringIndex = util::hostToDevice32(entry.name.getIndex());
+            symbolEntryData->name.index = util::hostToDevice32(entry.name.getIndex());
 
             // The symbols were all calculated with the packageBuffer offset. We need to
             // add the beginning of the output buffer.
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
new file mode 100644
index 0000000..b36682d
--- /dev/null
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -0,0 +1,89 @@
+/*
+ * 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 "java/AnnotationProcessor.h"
+#include "util/Util.h"
+
+#include <algorithm>
+
+namespace aapt {
+
+void AnnotationProcessor::appendCommentLine(const std::string& comment) {
+    static const std::string sDeprecated = "@deprecated";
+    static const std::string sSystemApi = "@SystemApi";
+
+    if (comment.find(sDeprecated) != std::string::npos && !mDeprecated) {
+        mDeprecated = true;
+        if (!mAnnotations.empty()) {
+            mAnnotations += "\n";
+        }
+        mAnnotations += mPrefix;
+        mAnnotations += "@Deprecated";
+    }
+
+    if (comment.find(sSystemApi) != std::string::npos && !mSystemApi) {
+        mSystemApi = true;
+        if (!mAnnotations.empty()) {
+            mAnnotations += "\n";
+        }
+        mAnnotations += mPrefix;
+        mAnnotations += "@android.annotations.SystemApi";
+    }
+
+    if (mComment.empty()) {
+        mComment += mPrefix;
+        mComment += "/**";
+    }
+
+    mComment += "\n";
+    mComment += mPrefix;
+    mComment += " * ";
+    mComment += std::move(comment);
+}
+
+void AnnotationProcessor::appendComment(const StringPiece16& comment) {
+    // We need to process line by line to clean-up whitespace and append prefixes.
+    for (StringPiece16 line : util::tokenize(comment, u'\n')) {
+        line = util::trimWhitespace(line);
+        if (!line.empty()) {
+            appendCommentLine(util::utf16ToUtf8(line));
+        }
+    }
+}
+
+void AnnotationProcessor::appendComment(const StringPiece& comment) {
+    for (StringPiece line : util::tokenize(comment, '\n')) {
+        line = util::trimWhitespace(line);
+        if (!line.empty()) {
+            appendCommentLine(line.toString());
+        }
+    }
+}
+
+std::string AnnotationProcessor::buildComment() {
+    if (!mComment.empty()) {
+        mComment += "\n";
+        mComment += mPrefix;
+        mComment += " */";
+    }
+    return std::move(mComment);
+}
+
+std::string AnnotationProcessor::buildAnnotations() {
+    return std::move(mAnnotations);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
new file mode 100644
index 0000000..81a6f6e
--- /dev/null
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -0,0 +1,94 @@
+/*
+ * 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 AAPT_JAVA_ANNOTATIONPROCESSOR_H
+#define AAPT_JAVA_ANNOTATIONPROCESSOR_H
+
+#include "util/StringPiece.h"
+
+#include <string>
+
+namespace aapt {
+
+/**
+ * Builds a JavaDoc comment from a set of XML comments.
+ * This will also look for instances of @SystemApi and convert them to
+ * actual Java annotations.
+ *
+ * Example:
+ *
+ * Input XML:
+ *
+ * <!-- This is meant to be hidden because
+ *      It is system api. Also it is @deprecated
+ *      @SystemApi
+ *      -->
+ *
+ * Output JavaDoc:
+ *
+ *  /\*
+ *   * This is meant to be hidden because
+ *   * It is system api. Also it is @deprecated
+ *   * @SystemApi
+ *   *\/
+ *
+ * Output Annotations:
+ *
+ * @Deprecated
+ * @android.annotation.SystemApi
+ *
+ */
+class AnnotationProcessor {
+public:
+    /**
+     * Creates an AnnotationProcessor with a given prefix for each line generated.
+     * This is usually a set of spaces for indentation.
+     */
+    AnnotationProcessor(const StringPiece& prefix) : mPrefix(prefix.toString()) {
+    }
+
+    /**
+     * Adds more comments. Since resources can have various values with different configurations,
+     * we need to collect all the comments.
+     */
+    void appendComment(const StringPiece16& comment);
+    void appendComment(const StringPiece& comment);
+
+    /**
+     * Finishes the comment and moves it to the caller. Subsequent calls to buildComment() have
+     * undefined results.
+     */
+    std::string buildComment();
+
+    /**
+     * Finishes the annotation and moves it to the caller. Subsequent calls to buildAnnotations()
+     * have undefined results.
+     */
+    std::string buildAnnotations();
+
+private:
+    std::string mPrefix;
+    std::string mComment;
+    std::string mAnnotations;
+    bool mDeprecated = false;
+    bool mSystemApi = false;
+
+    void appendCommentLine(const std::string& line);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_JAVA_ANNOTATIONPROCESSOR_H */
diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
similarity index 61%
rename from tools/aapt2/JavaClassGenerator.cpp
rename to tools/aapt2/java/JavaClassGenerator.cpp
index cdf1b6a..dfd2ef6 100644
--- a/tools/aapt2/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
-#include "JavaClassGenerator.h"
 #include "NameMangler.h"
 #include "Resource.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
+#include "ValueVisitor.h"
+
+#include "java/AnnotationProcessor.h"
+#include "java/JavaClassGenerator.h"
 #include "util/StringPiece.h"
 
 #include <algorithm>
@@ -108,13 +111,13 @@
     std::sort(sortedAttributes.begin(), sortedAttributes.end());
 
     // First we emit the array containing the IDs of each attribute.
-    *out << "        "
+    *out << "    "
          << "public static final int[] " << transform(entryName) << " = {";
 
     const size_t attrCount = sortedAttributes.size();
     for (size_t i = 0; i < attrCount; i++) {
         if (i % kAttribsPerLine == 0) {
-            *out << "\n            ";
+            *out << "\n      ";
         }
 
         *out << sortedAttributes[i].first;
@@ -122,11 +125,11 @@
             *out << ", ";
         }
     }
-    *out << "\n        };\n";
+    *out << "\n    };\n";
 
     // Now we emit the indices into the array.
     for (size_t i = 0; i < attrCount; i++) {
-        *out << "        "
+        *out << "    "
              << "public static" << finalModifier
              << " int " << transform(entryName);
 
@@ -140,6 +143,85 @@
     }
 }
 
+static void addAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) {
+    const uint32_t typeMask = attr->typeMask;
+    if (typeMask & android::ResTable_map::TYPE_REFERENCE) {
+        processor->appendComment(
+                "<p>May be a reference to another resource, in the form\n"
+                "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a theme\n"
+                "attribute in the form\n"
+                "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\".");
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_STRING) {
+        processor->appendComment(
+                "<p>May be a string value, using '\\;' to escape characters such as\n"
+                "'\\n' or '\\uxxxx' for a unicode character;");
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_INTEGER) {
+        processor->appendComment("<p>May be an integer value, such as \"<code>100</code>\".");
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+        processor->appendComment(
+                "<p>May be a boolean value, such as \"<code>true</code>\" or\n"
+                "\"<code>false</code>\".");
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_COLOR) {
+        processor->appendComment(
+                "<p>May be a color value, in the form of \"<code>#<i>rgb</i></code>\",\n"
+                "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n"
+                "\"<code>#<i>aarrggbb</i></code>\".");
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_FLOAT) {
+        processor->appendComment(
+                "<p>May be a floating point value, such as \"<code>1.2</code>\".");
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_DIMENSION) {
+        processor->appendComment(
+                "<p>May be a dimension value, which is a floating point number appended with a\n"
+                "unit such as \"<code>14.5sp</code>\".\n"
+                "Available units are: px (pixels), dp (density-independent pixels),\n"
+                "sp (scaled pixels based on preferred font size), in (inches), and\n"
+                "mm (millimeters).");
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_FRACTION) {
+        processor->appendComment(
+                "<p>May be a fractional value, which is a floating point number appended with\n"
+                "either % or %p, such as \"<code>14.5%</code>\".\n"
+                "The % suffix always means a percentage of the base size;\n"
+                "the optional %p suffix provides a size relative to some parent container.");
+    }
+
+    if (typeMask & (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
+        if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+            processor->appendComment(
+                    "<p>Must be one or more (separated by '|') of the following "
+                    "constant values.</p>");
+        } else {
+            processor->appendComment("<p>Must be one of the following constant values.</p>");
+        }
+
+        processor->appendComment("<table>\n<colgroup align=\"left\" />\n"
+                                 "<colgroup align=\"left\" />\n"
+                                 "<colgroup align=\"left\" />\n"
+                                 "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n");
+        for (const Attribute::Symbol& symbol : attr->symbols) {
+            std::stringstream line;
+            line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>"
+                 << "<td>" << std::hex << symbol.value << std::dec << "</td>"
+                 << "<td>" << util::trimWhitespace(symbol.symbol.getComment()) << "</td></tr>";
+            processor->appendComment(line.str());
+        }
+        processor->appendComment("</table>");
+    }
+}
+
 bool JavaClassGenerator::generateType(const StringPiece16& packageNameToGenerate,
                                       const ResourceTablePackage* package,
                                       const ResourceTableType* type,
@@ -185,7 +267,33 @@
             generateStyleable(packageNameToGenerate, unmangledName, static_cast<const Styleable*>(
                     entry->values.front().value.get()), out);
         } else {
-            *out << "        " << "public static" << finalModifier
+            AnnotationProcessor processor("    ");
+            if (entry->symbolStatus.state != SymbolState::kUndefined) {
+                processor.appendComment(entry->symbolStatus.comment);
+            }
+
+            for (const auto& configValue : entry->values) {
+                processor.appendComment(configValue.value->getComment());
+            }
+
+            if (!entry->values.empty()) {
+                if (Attribute* attr = valueCast<Attribute>(entry->values.front().value.get())) {
+                    // We list out the available values for the given attribute.
+                    addAttributeFormatDoc(&processor, attr);
+                }
+            }
+
+            std::string comment = processor.buildComment();
+            if (!comment.empty()) {
+                *out << comment << "\n";
+            }
+
+            std::string annotations = processor.buildAnnotations();
+            if (!annotations.empty()) {
+                *out << annotations << "\n";
+            }
+
+            *out << "    " << "public static" << finalModifier
                  << " int " << transform(unmangledName) << " = " << id << ";\n";
         }
     }
@@ -210,11 +318,11 @@
             } else {
                 typeStr = toString(type->type);
             }
-            *out << "    public static final class " << typeStr << " {\n";
+            *out << "  public static final class " << typeStr << " {\n";
             if (!generateType(packageNameToGenerate, package.get(), type.get(), out)) {
                 return false;
             }
-            *out << "    }\n";
+            *out << "  }\n";
         }
     }
 
diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
similarity index 100%
rename from tools/aapt2/JavaClassGenerator.h
rename to tools/aapt2/java/JavaClassGenerator.h
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
similarity index 89%
rename from tools/aapt2/JavaClassGenerator_test.cpp
rename to tools/aapt2/java/JavaClassGenerator_test.cpp
index cc5e981..2dc387b 100644
--- a/tools/aapt2/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "JavaClassGenerator.h"
+#include "java/JavaClassGenerator.h"
 #include "util/Util.h"
 
 #include "test/Builders.h"
@@ -198,4 +198,35 @@
     EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar ="));
 }
 
+TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addSimple(u"@android:id/foo", ResourceId(0x01010000))
+            .build();
+    test::getValue<Id>(table.get(), u"@android:id/foo")
+            ->setComment(std::u16string(u"This is a comment\n@deprecated"));
+
+    JavaClassGenerator generator(table.get(), {});
+
+    std::stringstream out;
+    ASSERT_TRUE(generator.generate(u"android", &out));
+    std::string actual = out.str();
+
+    EXPECT_NE(std::string::npos, actual.find(
+    R"EOF(/**
+     * This is a comment
+     * @deprecated
+     */
+    @Deprecated
+    public static final int foo = 0x01010000;)EOF"));
+}
+
+TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {
+
+}
+
+TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) {
+
+}
+
 } // namespace aapt
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
new file mode 100644
index 0000000..901a344
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -0,0 +1,131 @@
+/*
+ * 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 "Source.h"
+#include "XmlDom.h"
+
+#include "java/AnnotationProcessor.h"
+#include "java/ManifestClassGenerator.h"
+#include "util/Maybe.h"
+
+#include <algorithm>
+
+namespace aapt {
+
+static Maybe<StringPiece16> extractJavaIdentifier(IDiagnostics* diag, const Source& source,
+                                                  const StringPiece16& value) {
+    const StringPiece16 sep = u".";
+    auto iter = std::find_end(value.begin(), value.end(), sep.begin(), sep.end());
+
+    StringPiece16 result;
+    if (iter != value.end()) {
+        result.assign(iter + sep.size(), value.end() - (iter + sep.size()));
+    } else {
+        result = value;
+    }
+
+    if (result.empty()) {
+        diag->error(DiagMessage(source) << "empty symbol");
+        return {};
+    }
+
+    iter = util::findNonAlphaNumericAndNotInSet(result, u"_");
+    if (iter != result.end()) {
+        diag->error(DiagMessage(source)
+                    << "invalid character '" << StringPiece16(iter, 1)
+                    << "' in '" << result << "'");
+        return {};
+    }
+
+    if (*result.begin() >= u'0' && *result.begin() <= u'9') {
+        diag->error(DiagMessage(source) << "symbol can not start with a digit");
+        return {};
+    }
+
+    return result;
+}
+
+static bool writeSymbol(IDiagnostics* diag, const Source& source, xml::Element* el,
+                        std::ostream* out) {
+    xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name");
+    if (!attr) {
+        diag->error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'");
+        return false;
+    }
+
+    Maybe<StringPiece16> result = extractJavaIdentifier(diag, source.withLine(el->lineNumber),
+                                                        attr->value);
+    if (!result) {
+        return false;
+    }
+
+    *out << "\n";
+
+    if (!util::trimWhitespace(el->comment).empty()) {
+        AnnotationProcessor processor("    ");
+        processor.appendComment(el->comment);
+        *out << processor.buildComment() << "\n";
+        std::string annotations = processor.buildAnnotations();
+        if (!annotations.empty()) {
+            *out << annotations << "\n";
+        }
+    }
+    *out << "    public static final String " << result.value() << "=\"" << attr->value << "\";\n";
+    return true;
+}
+
+bool ManifestClassGenerator::generate(IDiagnostics* diag, const StringPiece16& package,
+                                      XmlResource* res, std::ostream* out) {
+    xml::Element* el = xml::findRootElement(res->root.get());
+    if (!el) {
+        return false;
+    }
+
+    if (el->name != u"manifest" && !el->namespaceUri.empty()) {
+        diag->error(DiagMessage(res->file.source) << "no <manifest> root tag defined");
+        return false;
+    }
+
+    *out << "package " << package << ";\n\n"
+         << "public class Manifest {\n";
+
+    bool error = false;
+    std::vector<xml::Element*> children = el->getChildElements();
+
+
+    // First write out permissions.
+    *out << "  public static class permission {\n";
+    for (xml::Element* childEl : children) {
+        if (childEl->namespaceUri.empty() && childEl->name == u"permission") {
+            error |= !writeSymbol(diag, res->file.source, childEl, out);
+        }
+    }
+    *out << "  }\n";
+
+    // Next write out permission groups.
+    *out << "  public static class permission_group {\n";
+    for (xml::Element* childEl : children) {
+        if (childEl->namespaceUri.empty() && childEl->name == u"permission-group") {
+            error |= !writeSymbol(diag, res->file.source, childEl, out);
+        }
+    }
+    *out << "  }\n";
+
+    *out << "}\n";
+    return !error;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h
new file mode 100644
index 0000000..0f0998f
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator.h
@@ -0,0 +1,35 @@
+/*
+ * 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 AAPT_JAVA_MANIFESTCLASSGENERATOR_H
+#define AAPT_JAVA_MANIFESTCLASSGENERATOR_H
+
+#include "Diagnostics.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/StringPiece.h"
+
+#include <iostream>
+
+namespace aapt {
+
+struct ManifestClassGenerator {
+    bool generate(IDiagnostics* diag, const StringPiece16& package, XmlResource* res,
+                  std::ostream* out);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_JAVA_MANIFESTCLASSGENERATOR_H */
diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
new file mode 100644
index 0000000..1b5bc05
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -0,0 +1,120 @@
+/*
+ * 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 "java/ManifestClassGenerator.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+        <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+          <permission android:name="android.permission.ACCESS_INTERNET" />
+          <permission android:name="android.DO_DANGEROUS_THINGS" />
+          <permission android:name="com.test.sample.permission.HUH" />
+          <permission-group android:name="foo.bar.PERMISSION" />
+        </manifest>)EOF");
+
+    std::stringstream out;
+    ManifestClassGenerator generator;
+    ASSERT_TRUE(generator.generate(context->getDiagnostics(), u"android", manifest.get(), &out));
+
+    std::string actual = out.str();
+
+    const size_t permissionClassPos = actual.find("public static class permission {");
+    const size_t permissionGroupClassPos = actual.find("public static class permission_group {");
+    ASSERT_NE(std::string::npos, permissionClassPos);
+    ASSERT_NE(std::string::npos, permissionGroupClassPos);
+
+    //
+    // Make sure these permissions are in the permission class.
+    //
+
+    size_t pos = actual.find("public static final String ACCESS_INTERNET="
+                             "\"android.permission.ACCESS_INTERNET\";");
+    EXPECT_GT(pos, permissionClassPos);
+    EXPECT_LT(pos, permissionGroupClassPos);
+
+    pos = actual.find("public static final String DO_DANGEROUS_THINGS="
+                      "\"android.DO_DANGEROUS_THINGS\";");
+    EXPECT_GT(pos, permissionClassPos);
+    EXPECT_LT(pos, permissionGroupClassPos);
+
+    pos = actual.find("public static final String HUH=\"com.test.sample.permission.HUH\";");
+    EXPECT_GT(pos, permissionClassPos);
+    EXPECT_LT(pos, permissionGroupClassPos);
+
+    //
+    // Make sure these permissions are in the permission_group class
+    //
+
+    pos = actual.find("public static final String PERMISSION="
+                      "\"foo.bar.PERMISSION\";");
+    EXPECT_GT(pos, permissionGroupClassPos);
+    EXPECT_LT(pos, std::string::npos);
+}
+
+TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+        <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+          <!-- Required to access the internet.
+               Added in API 1. -->
+          <permission android:name="android.permission.ACCESS_INTERNET" />
+          <!-- @deprecated This permission is for playing outside. -->
+          <permission android:name="android.permission.PLAY_OUTSIDE" />
+          <!-- This is a private permission for system only!
+               @hide
+               @SystemApi -->
+          <permission android:name="android.permission.SECRET" />
+        </manifest>)EOF");
+
+    std::stringstream out;
+    ManifestClassGenerator generator;
+    ASSERT_TRUE(generator.generate(context->getDiagnostics(), u"android", manifest.get(), &out));
+
+    std::string actual = out.str();
+
+    EXPECT_NE(std::string::npos, actual.find(
+R"EOF(    /**
+     * Required to access the internet.
+     * Added in API 1.
+     */
+    public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)EOF"));
+
+    EXPECT_NE(std::string::npos, actual.find(
+R"EOF(    /**
+     * @deprecated This permission is for playing outside.
+     */
+    @Deprecated
+    public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)EOF"));
+
+    EXPECT_NE(std::string::npos, actual.find(
+R"EOF(    /**
+     * This is a private permission for system only!
+     * @hide
+     * @SystemApi
+     */
+    @android.annotations.SystemApi
+    public static final String SECRET="android.permission.SECRET";)EOF"));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
similarity index 91%
rename from tools/aapt2/ProguardRules.cpp
rename to tools/aapt2/java/ProguardRules.cpp
index 7f4dc91..4431477 100644
--- a/tools/aapt2/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#include "ProguardRules.h"
 #include "XmlDom.h"
 
+#include "java/ProguardRules.h"
 #include "util/Util.h"
 
 #include <memory>
@@ -25,8 +25,6 @@
 namespace aapt {
 namespace proguard {
 
-constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
-
 class BaseVisitor : public xml::Visitor {
 public:
     BaseVisitor(const Source& source, KeepSet* keepSet) : mSource(source), mKeepSet(keepSet) {
@@ -83,7 +81,7 @@
         bool checkName = false;
         if (node->namespaceUri.empty()) {
             checkClass = node->name == u"view" || node->name == u"fragment";
-        } else if (node->namespaceUri == kSchemaAndroid) {
+        } else if (node->namespaceUri == xml::kSchemaAndroid) {
             checkName = node->name == u"fragment";
         }
 
@@ -91,10 +89,10 @@
             if (checkClass && attr.namespaceUri.empty() && attr.name == u"class" &&
                     util::isJavaClassName(attr.value)) {
                 addClass(node->lineNumber, attr.value);
-            } else if (checkName && attr.namespaceUri == kSchemaAndroid && attr.name == u"name" &&
-                    util::isJavaClassName(attr.value)) {
+            } else if (checkName && attr.namespaceUri == xml::kSchemaAndroid &&
+                    attr.name == u"name" && util::isJavaClassName(attr.value)) {
                 addClass(node->lineNumber, attr.value);
-            } else if (attr.namespaceUri == kSchemaAndroid && attr.name == u"onClick") {
+            } else if (attr.namespaceUri == xml::kSchemaAndroid && attr.name == u"onClick") {
                 addMethod(node->lineNumber, attr.value);
             }
         }
@@ -114,7 +112,7 @@
         }
 
         if (checkFragment) {
-            xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"fragment");
+            xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"fragment");
             if (attr && util::isJavaClassName(attr->value)) {
                 addClass(node->lineNumber, attr->value);
             }
@@ -156,7 +154,7 @@
                 }
             } else if (node->name == u"application") {
                 getName = true;
-                xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"backupAgent");
+                xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"backupAgent");
                 if (attr) {
                     Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
                                                                                     attr->value);
@@ -171,7 +169,7 @@
             }
 
             if (getName) {
-                xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"name");
+                xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"name");
                 if (attr) {
                     Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
                                                                                     attr->value);
diff --git a/tools/aapt2/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
similarity index 100%
rename from tools/aapt2/ProguardRules.h
rename to tools/aapt2/java/ProguardRules.h
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index ad701de..0236e98 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -17,16 +17,18 @@
 #include "AppInfo.h"
 #include "Debug.h"
 #include "Flags.h"
-#include "JavaClassGenerator.h"
 #include "NameMangler.h"
-#include "ProguardRules.h"
 #include "XmlDom.h"
 
 #include "compile/IdAssigner.h"
 #include "flatten/Archive.h"
 #include "flatten/TableFlattener.h"
 #include "flatten/XmlFlattener.h"
+#include "java/JavaClassGenerator.h"
+#include "java/ManifestClassGenerator.h"
+#include "java/ProguardRules.h"
 #include "link/Linkers.h"
+#include "link/ManifestFixer.h"
 #include "link/TableMerger.h"
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
@@ -53,6 +55,8 @@
     bool verbose = false;
     bool outputToDirectory = false;
     Maybe<std::u16string> privateSymbols;
+    Maybe<std::u16string> minSdkVersionDefault;
+    Maybe<std::u16string> targetSdkVersionDefault;
 };
 
 struct LinkContext : public IAaptContext {
@@ -239,15 +243,8 @@
     }
 
     Maybe<AppInfo> extractAppInfoFromManifest(XmlResource* xmlRes) {
-        xml::Node* node = xmlRes->root.get();
-
-        // Find the first xml::Element.
-        while (node && !xml::nodeCast<xml::Element>(node)) {
-            node = !node->children.empty() ? node->children.front().get() : nullptr;
-        }
-
         // Make sure the first element is <manifest> with package attribute.
-        if (xml::Element* manifestEl = xml::nodeCast<xml::Element>(node)) {
+        if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
             if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
                 if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
                     return AppInfo{ packageAttr->value };
@@ -354,6 +351,36 @@
         return true;
     }
 
+    bool writeManifestJavaFile(XmlResource* manifestXml) {
+        if (!mOptions.generateJavaClassPath) {
+            return true;
+        }
+
+        std::string outPath = mOptions.generateJavaClassPath.value();
+        file::appendPath(&outPath,
+                         file::packageToPath(util::utf16ToUtf8(mContext.getCompilationPackage())));
+        file::mkdirs(outPath);
+        file::appendPath(&outPath, "Manifest.java");
+
+        std::ofstream fout(outPath, std::ofstream::binary);
+        if (!fout) {
+            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            return false;
+        }
+
+        ManifestClassGenerator generator;
+        if (!generator.generate(mContext.getDiagnostics(), mContext.getCompilationPackage(),
+                                manifestXml, &fout)) {
+            return false;
+        }
+
+        if (!fout) {
+            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+            return false;
+        }
+        return true;
+    }
+
     bool writeProguardFile(const proguard::KeepSet& keepSet) {
         if (!mOptions.generateProguardRulesPath) {
             return true;
@@ -539,15 +566,28 @@
         }
 
         {
+            ManifestFixerOptions manifestFixerOptions;
+            manifestFixerOptions.minSdkVersionDefault = mOptions.minSdkVersionDefault;
+            manifestFixerOptions.targetSdkVersionDefault = mOptions.targetSdkVersionDefault;
+            ManifestFixer manifestFixer(manifestFixerOptions);
+            if (!manifestFixer.consume(&mContext, manifestXml.get())) {
+                error = true;
+            }
+
             XmlReferenceLinker manifestLinker;
             if (manifestLinker.consume(&mContext, manifestXml.get())) {
-
                 if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
                                                                manifestXml.get(),
                                                                &proguardKeepSet)) {
                     error = true;
                 }
 
+                if (mOptions.generateJavaClassPath) {
+                    if (!writeManifestJavaFile(manifestXml.get())) {
+                        error = true;
+                    }
+                }
+
                 if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
                                 archiveWriter.get())) {
                     error = true;
@@ -705,6 +745,7 @@
 int link(const std::vector<StringPiece>& args) {
     LinkOptions options;
     Maybe<std::string> privateSymbolsPackage;
+    Maybe<std::string> minSdkVersion, targetSdkVersion;
     Flags flags = Flags()
             .requiredFlag("-o", "Output path", &options.outputPath)
             .requiredFlag("--manifest", "Path to the Android manifest to build",
@@ -720,10 +761,15 @@
             .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
                             "by -o",
                             &options.outputToDirectory)
+            .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
+                          "AndroidManifest.xml", &minSdkVersion)
+            .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
+                          "AndroidManifest.xml", &targetSdkVersion)
             .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
             .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
-                          "private symbols. If not specified, public and private symbols will "
-                          "use the application's package name", &privateSymbolsPackage)
+                          "private symbols.\n"
+                          "If not specified, public and private symbols will use the application's "
+                          "package name", &privateSymbolsPackage)
             .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
 
     if (!flags.parse("aapt2 link", args, &std::cerr)) {
@@ -734,6 +780,14 @@
         options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
     }
 
+    if (minSdkVersion) {
+        options.minSdkVersionDefault = util::utf8ToUtf16(minSdkVersion.value());
+    }
+
+    if (targetSdkVersion) {
+        options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value());
+    }
+
     LinkCommand cmd = { options };
     return cmd.run(flags.getArgs());
 }
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
new file mode 100644
index 0000000..52d9426
--- /dev/null
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -0,0 +1,100 @@
+/*
+ * 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 "ResourceUtils.h"
+#include "XmlDom.h"
+
+#include "link/ManifestFixer.h"
+#include "util/Util.h"
+
+namespace aapt {
+
+static bool verifyManifest(IAaptContext* context, const Source& source, xml::Element* manifestEl) {
+    bool error = false;
+
+    xml::Attribute* attr = manifestEl->findAttribute({}, u"package");
+    if (!attr) {
+        context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
+                                         << "missing 'package' attribute");
+        error = true;
+    } else if (ResourceUtils::isReference(attr->value)) {
+        context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
+                                         << "value for attribute 'package' must not be a "
+                                            "reference");
+        error = true;
+    } else if (!util::isJavaPackageName(attr->value)) {
+        context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
+                                         << "invalid package name '" << attr->value << "'");
+        error = true;
+    }
+
+    return !error;
+}
+
+static bool fixUsesSdk(IAaptContext* context, const Source& source, xml::Element* el,
+                       const ManifestFixerOptions& options) {
+    if (options.minSdkVersionDefault &&
+            el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion") == nullptr) {
+        // There was no minSdkVersion defined and we have a default to assign.
+        el->attributes.push_back(xml::Attribute{
+                xml::kSchemaAndroid, u"minSdkVersion", options.minSdkVersionDefault.value() });
+    }
+
+    if (options.targetSdkVersionDefault &&
+            el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion") == nullptr) {
+        // There was no targetSdkVersion defined and we have a default to assign.
+        el->attributes.push_back(xml::Attribute{
+                xml::kSchemaAndroid, u"targetSdkVersion",
+                options.targetSdkVersionDefault.value() });
+    }
+    return true;
+}
+
+bool ManifestFixer::consume(IAaptContext* context, XmlResource* doc) {
+    xml::Element* root = xml::findRootElement(doc->root.get());
+    if (!root || !root->namespaceUri.empty() || root->name != u"manifest") {
+        context->getDiagnostics()->error(DiagMessage(doc->file.source)
+                                         << "root tag must be <manifest>");
+        return false;
+    }
+
+    if (!verifyManifest(context, doc->file.source, root)) {
+        return false;
+    }
+
+    bool foundUsesSdk = false;
+    for (xml::Element* el : root->getChildElements()) {
+        if (!el->namespaceUri.empty()) {
+            continue;
+        }
+
+        if (el->name == u"uses-sdk") {
+            foundUsesSdk = true;
+            fixUsesSdk(context, doc->file.source, el, mOptions);
+        }
+    }
+
+    if (!foundUsesSdk && (mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault)) {
+        std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>();
+        usesSdk->name = u"uses-sdk";
+        fixUsesSdk(context, doc->file.source, usesSdk.get(), mOptions);
+        root->addChild(std::move(usesSdk));
+    }
+
+    return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
new file mode 100644
index 0000000..16e161d
--- /dev/null
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -0,0 +1,44 @@
+/*
+ * 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 AAPT_LINK_MANIFESTFIXER_H
+#define AAPT_LINK_MANIFESTFIXER_H
+
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+struct ManifestFixerOptions {
+    Maybe<std::u16string> minSdkVersionDefault;
+    Maybe<std::u16string> targetSdkVersionDefault;
+};
+
+/**
+ * Verifies that the manifest is correctly formed and inserts defaults
+ * where specified with ManifestFixerOptions.
+ */
+struct ManifestFixer : public IXmlResourceConsumer {
+    ManifestFixerOptions mOptions;
+
+    ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) {
+    }
+
+    bool consume(IAaptContext* context, XmlResource* doc) override;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINK_MANIFESTFIXER_H */
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
new file mode 100644
index 0000000..5c5d8af
--- /dev/null
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -0,0 +1,165 @@
+/*
+ * 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 "link/ManifestFixer.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+struct ManifestFixerTest : public ::testing::Test {
+    std::unique_ptr<IAaptContext> mContext;
+
+    void SetUp() override {
+        mContext = test::ContextBuilder()
+                .setCompilationPackage(u"android")
+                .setPackageId(0x01)
+                .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+                .setSymbolTable(test::StaticSymbolTableBuilder()
+                        .addSymbol(u"@android:attr/package", ResourceId(0x01010000),
+                                   test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_STRING)
+                                        .build())
+                        .addSymbol(u"@android:attr/minSdkVersion", ResourceId(0x01010001),
+                                   test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_STRING |
+                                                     android::ResTable_map::TYPE_INTEGER)
+                                        .build())
+                        .addSymbol(u"@android:attr/targetSdkVersion", ResourceId(0x01010002),
+                                   test::AttributeBuilder()
+                                        .setTypeMask(android::ResTable_map::TYPE_STRING |
+                                                     android::ResTable_map::TYPE_INTEGER)
+                                        .build())
+                        .addSymbol(u"@android:string/str", ResourceId(0x01060000))
+                        .build())
+                .build();
+    }
+
+    std::unique_ptr<XmlResource> verify(const StringPiece& str) {
+        return verifyWithOptions(str, {});
+    }
+
+    std::unique_ptr<XmlResource> verifyWithOptions(const StringPiece& str,
+                                                   const ManifestFixerOptions& options) {
+        std::unique_ptr<XmlResource> doc = test::buildXmlDom(str);
+        ManifestFixer fixer(options);
+        if (fixer.consume(mContext.get(), doc.get())) {
+            return doc;
+        }
+        return {};
+    }
+};
+
+TEST_F(ManifestFixerTest, EnsureManifestIsRootTag) {
+    EXPECT_EQ(nullptr, verify("<other-tag />"));
+    EXPECT_EQ(nullptr, verify("<ns:manifest xmlns:ns=\"com\" />"));
+    EXPECT_NE(nullptr, verify("<manifest package=\"android\"></manifest>"));
+}
+
+TEST_F(ManifestFixerTest, EnsureManifestHasPackage) {
+    EXPECT_NE(nullptr, verify("<manifest package=\"android\" />"));
+    EXPECT_NE(nullptr, verify("<manifest package=\"com.android\" />"));
+    EXPECT_NE(nullptr, verify("<manifest package=\"com.android.google\" />"));
+    EXPECT_EQ(nullptr, verify("<manifest package=\"com.android.google.Class$1\" />"));
+    EXPECT_EQ(nullptr,
+              verify("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" "
+                     "android:package=\"com.android\" />"));
+    EXPECT_EQ(nullptr, verify("<manifest package=\"@string/str\" />"));
+}
+
+
+
+TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
+    ManifestFixerOptions options = { std::u16string(u"8"), std::u16string(u"22") };
+
+    std::unique_ptr<XmlResource> doc = verifyWithOptions(R"EOF(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+                package="android">
+        <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
+      </manifest>)EOF", options);
+    ASSERT_NE(nullptr, doc);
+
+    xml::Element* el;
+    xml::Attribute* attr;
+
+    el = xml::findRootElement(doc->root.get());
+    ASSERT_NE(nullptr, el);
+    el = el->findChild({}, u"uses-sdk");
+    ASSERT_NE(nullptr, el);
+    attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+    ASSERT_NE(nullptr, attr);
+    EXPECT_EQ(u"7", attr->value);
+    attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+    ASSERT_NE(nullptr, attr);
+    EXPECT_EQ(u"21", attr->value);
+
+    doc = verifyWithOptions(R"EOF(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+                package="android">
+        <uses-sdk android:targetSdkVersion="21" />
+      </manifest>)EOF", options);
+    ASSERT_NE(nullptr, doc);
+
+    el = xml::findRootElement(doc->root.get());
+    ASSERT_NE(nullptr, el);
+    el = el->findChild({}, u"uses-sdk");
+    ASSERT_NE(nullptr, el);
+    attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+    ASSERT_NE(nullptr, attr);
+    EXPECT_EQ(u"8", attr->value);
+    attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+    ASSERT_NE(nullptr, attr);
+    EXPECT_EQ(u"21", attr->value);
+
+    doc = verifyWithOptions(R"EOF(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+                package="android">
+        <uses-sdk />
+      </manifest>)EOF", options);
+    ASSERT_NE(nullptr, doc);
+
+    el = xml::findRootElement(doc->root.get());
+    ASSERT_NE(nullptr, el);
+    el = el->findChild({}, u"uses-sdk");
+    ASSERT_NE(nullptr, el);
+    attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+    ASSERT_NE(nullptr, attr);
+    EXPECT_EQ(u"8", attr->value);
+    attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+    ASSERT_NE(nullptr, attr);
+    EXPECT_EQ(u"22", attr->value);
+
+    doc = verifyWithOptions(R"EOF(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+                package="android" />)EOF", options);
+    ASSERT_NE(nullptr, doc);
+
+    el = xml::findRootElement(doc->root.get());
+    ASSERT_NE(nullptr, el);
+    el = el->findChild({}, u"uses-sdk");
+    ASSERT_NE(nullptr, el);
+    attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+    ASSERT_NE(nullptr, attr);
+    EXPECT_EQ(u"8", attr->value);
+    attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+    ASSERT_NE(nullptr, attr);
+    EXPECT_EQ(u"22", attr->value);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 1b510e7..89cd972 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -19,8 +19,8 @@
 
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "util/Util.h"
 #include "XmlDom.h"
+#include "util/Util.h"
 
 #include "test/Common.h"
 
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 314c1e8..0d17e84 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -116,7 +116,7 @@
         if (util::deviceToHost32(mSymbolEntries[i].offset) == offset) {
             // This offset is a symbol!
             const StringPiece16 str = util::getString(
-                    mSymbolPool, util::deviceToHost32(mSymbolEntries[i].stringIndex));
+                    mSymbolPool, util::deviceToHost32(mSymbolEntries[i].name.index));
 
             StringPiece16 typeStr;
             ResourceUtils::extractResourceName(str, &outSymbol->package, &typeStr,
@@ -425,8 +425,14 @@
         Symbol symbol;
         if (mSourcePool.getError() == NO_ERROR) {
             symbol.source.path = util::utf16ToUtf8(util::getString(
-                    mSourcePool, util::deviceToHost32(entry->source.index)));
-            symbol.source.line = util::deviceToHost32(entry->sourceLine);
+                    mSourcePool, util::deviceToHost32(entry->source.path.index)));
+            symbol.source.line = util::deviceToHost32(entry->source.line);
+        }
+
+        StringPiece16 comment = util::getString(mSourcePool,
+                                                util::deviceToHost32(entry->source.comment.index));
+        if (!comment.empty()) {
+            symbol.comment = comment.toString();
         }
 
         switch (util::deviceToHost16(entry->state)) {
@@ -560,7 +566,7 @@
         Source source = mSource;
         if (sourceBlock) {
             size_t len;
-            const char* str = mSourcePool.string8At(util::deviceToHost32(sourceBlock->pathIndex),
+            const char* str = mSourcePool.string8At(util::deviceToHost32(sourceBlock->path.index),
                                                     &len);
             if (str) {
                 source.path.assign(str, len);
@@ -568,6 +574,12 @@
             source.line = util::deviceToHost32(sourceBlock->line);
         }
 
+        StringPiece16 comment = util::getString(mSourcePool,
+                                                util::deviceToHost32(sourceBlock->comment.index));
+        if (!comment.empty()) {
+            resourceValue->setComment(comment);
+        }
+
         resourceValue->setSource(source);
         if (!mTable->addResourceAllowMangled(name, config, std::move(resourceValue),
                                              mContext->getDiagnostics())) {
@@ -678,8 +690,6 @@
             // fallthrough
         case ResourceType::kAttr:
             return parseAttr(name, config, map);
-        case ResourceType::kIntegerArray:
-            // fallthrough
         case ResourceType::kArray:
             return parseArray(name, config, map);
         case ResourceType::kStyleable:
diff --git a/tools/aapt2/util/StringPiece.h b/tools/aapt2/util/StringPiece.h
index 8cbdeae..31deb45 100644
--- a/tools/aapt2/util/StringPiece.h
+++ b/tools/aapt2/util/StringPiece.h
@@ -36,6 +36,7 @@
 class BasicStringPiece {
 public:
     using const_iterator = const TChar*;
+    using difference_type = size_t;
 
     BasicStringPiece();
     BasicStringPiece(const BasicStringPiece<TChar>& str);
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index f219b65..59b8385 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -76,6 +76,25 @@
     return StringPiece16(start, end - start);
 }
 
+StringPiece trimWhitespace(const StringPiece& str) {
+    if (str.size() == 0 || str.data() == nullptr) {
+        return str;
+    }
+
+    const char* start = str.data();
+    const char* end = str.data() + str.length();
+
+    while (start != end && isspace(*start)) {
+        start++;
+    }
+
+    while (end != start && isspace(*(end - 1))) {
+        end--;
+    }
+
+    return StringPiece(start, end - start);
+}
+
 StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16& str,
         const StringPiece16& allowedChars) {
     const auto endIter = str.end();
@@ -170,6 +189,105 @@
     return result;
 }
 
+static size_t consumeDigits(const char16_t* start, const char16_t* end) {
+    const char16_t* c = start;
+    for (; c != end && *c >= u'0' && *c <= u'9'; c++) {}
+    return static_cast<size_t>(c - start);
+}
+
+bool verifyJavaStringFormat(const StringPiece16& str) {
+    const char16_t* c = str.begin();
+    const char16_t* const end = str.end();
+
+    size_t argCount = 0;
+    bool nonpositional = false;
+    while (c != end) {
+        if (*c == u'%' && c + 1 < end) {
+            c++;
+
+            if (*c == u'%') {
+                c++;
+                continue;
+            }
+
+            argCount++;
+
+            size_t numDigits = consumeDigits(c, end);
+            if (numDigits > 0) {
+                c += numDigits;
+                if (c != end && *c != u'$') {
+                    // The digits were a size, but not a positional argument.
+                    nonpositional = true;
+                }
+            } else if (*c == u'<') {
+                // Reusing last argument, bad idea since positions can be moved around
+                // during translation.
+                nonpositional = true;
+
+                c++;
+
+                // Optionally we can have a $ after
+                if (c != end && *c == u'$') {
+                    c++;
+                }
+            } else {
+                nonpositional = true;
+            }
+
+            // Ignore size, width, flags, etc.
+            while (c != end && (*c == u'-' ||
+                    *c == u'#' ||
+                    *c == u'+' ||
+                    *c == u' ' ||
+                    *c == u',' ||
+                    *c == u'(' ||
+                    (*c >= u'0' && *c <= '9'))) {
+                c++;
+            }
+
+            /*
+             * This is a shortcut to detect strings that are going to Time.format()
+             * instead of String.format()
+             *
+             * Comparison of String.format() and Time.format() args:
+             *
+             * String: ABC E GH  ST X abcdefgh  nost x
+             *   Time:    DEFGHKMS W Za  d   hkm  s w yz
+             *
+             * Therefore we know it's definitely Time if we have:
+             *     DFKMWZkmwyz
+             */
+            if (c != end) {
+                switch (*c) {
+                case 'D':
+                case 'F':
+                case 'K':
+                case 'M':
+                case 'W':
+                case 'Z':
+                case 'k':
+                case 'm':
+                case 'w':
+                case 'y':
+                case 'z':
+                    return true;
+                }
+            }
+        }
+
+        if (c != end) {
+            c++;
+        }
+    }
+
+    if (argCount > 1 && nonpositional) {
+        // Multiple arguments were specified, but some or all were non positional. Translated
+        // strings may rearrange the order of the arguments, which will break the string.
+        return false;
+    }
+    return true;
+}
+
 static Maybe<char16_t> parseUnicodeCodepoint(const char16_t** start, const char16_t* end) {
     char16_t code = 0;
     for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) {
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 402147d..80552a5 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -62,6 +62,8 @@
  */
 StringPiece16 trimWhitespace(const StringPiece16& str);
 
+StringPiece trimWhitespace(const StringPiece& str);
+
 /**
  * UTF-16 isspace(). It basically checks for lower range characters that are
  * whitespace.
@@ -156,6 +158,14 @@
     return StringPiece16();
 }
 
+/**
+ * Checks that the Java string format contains no non-positional arguments (arguments without
+ * explicitly specifying an index) when there are more than one argument. This is an error
+ * because translations may rearrange the order of the arguments in the string, which will
+ * break the string interpolation.
+ */
+bool verifyJavaStringFormat(const StringPiece16& str);
+
 class StringBuilder {
 public:
     StringBuilder& append(const StringPiece16& str);
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index cdba960..9db9fb7 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -185,4 +185,12 @@
     EXPECT_EQ(suffix, u".");
 }
 
+TEST(UtilTest, VerifyJavaStringFormat) {
+    ASSERT_TRUE(util::verifyJavaStringFormat(u"%09.34f"));
+    ASSERT_TRUE(util::verifyJavaStringFormat(u"%9$.34f %8$"));
+    ASSERT_TRUE(util::verifyJavaStringFormat(u"%% %%"));
+    ASSERT_FALSE(util::verifyJavaStringFormat(u"%09$f %f"));
+    ASSERT_FALSE(util::verifyJavaStringFormat(u"%09f %08s"));
+}
+
 } // namespace aapt
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 007d075..498be5a 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -513,4 +513,8 @@
         // TODO Auto-generated method stub
         return null;
     }
+
+    @Override
+    public void cancelTaskWindowTransition(int taskId) {
+    }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 2997907..11bd15d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -95,6 +95,13 @@
     }
 
     @Override
+    public void repositionChild(IWindow childWindow, int x, int y, long deferTransactionUntilFrame,
+            Rect outFrame) {
+        // pass for now.
+        return;
+    }
+
+    @Override
     public void performDeferredDestroy(IWindow window) {
         // pass for now.
     }