Merge "Remove almost-all android::Singleton users"
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/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/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/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/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/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_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..47df786 100644
--- a/core/jni/android_view_DisplayListCanvas.cpp
+++ b/core/jni/android_view_DisplayListCanvas.cpp
@@ -23,6 +23,7 @@
 #include <android_runtime/AndroidRuntime.h>
 
 #include <cutils/properties.h>
+#include <EGL/egl.h>
 
 #include <SkBitmap.h>
 #include <SkRegion.h>
@@ -146,15 +147,17 @@
 // ----------------------------------------------------------------------------
 
 static jboolean android_view_DisplayListCanvas_isAvailable(JNIEnv* env, jobject clazz) {
-    char prop[PROPERTY_VALUE_MAX];
-    if (property_get("ro.kernel.qemu", prop, NULL) == 0) {
-        // not in the emulator
-        return JNI_TRUE;
+    static EGLint numES2Configs = -1;
+
+    if (numES2Configs == -1) {
+        EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+        EGLint major; EGLint minor;
+        eglInitialize(display, &major, &minor);
+        EGLint configAttribs[] = {EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE};
+        eglChooseConfig(display, configAttribs, NULL, 0, &numES2Configs);
     }
-    // In the emulator this property will be set to 1 when hardware GLES 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 (numES2Configs > 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/symbols.xml b/core/res/res/values/symbols.xml
index d1932fc..ebef6cc 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2019,7 +2019,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/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 4368fe9..3405030 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) {
@@ -1800,9 +1798,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 +1819,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 +1870,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 +1926,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/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/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/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/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/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/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/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/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/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 141562c..bdd3440 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
@@ -310,14 +313,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 +338,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 +730,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/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/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index b266eaa..e7f07f7 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 */
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/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/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5aab804..9a17f2f 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;
@@ -8362,6 +8362,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;
@@ -8651,12 +8654,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;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index b5f424d..4897eb9 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;
@@ -371,7 +366,6 @@
         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;
             }
@@ -2797,8 +2789,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.
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 1cd71a1..7e42dce 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;
@@ -554,8 +564,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();
@@ -1820,8 +1830,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;
@@ -2899,15 +2908,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 +3033,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 +3054,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 +3198,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 +3283,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
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/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/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 22bedc3..4863603 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;
@@ -15086,6 +15087,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) {
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 5d0e83d..925acb8 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,9 @@
 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;
@@ -199,6 +202,14 @@
     @GuardedBy("mRestrictionsLock")
     private final SparseArray<Bundle> mCachedEffectiveUserRestrictions = new SparseArray<>();
 
+    /**
+     * User restrictions that have already been applied in {@link #applyUserRestrictionsRL}.  We
+     * use it to detect restrictions that have changed since the last
+     * {@link #applyUserRestrictionsRL} call.
+     */
+    @GuardedBy("mRestrictionsLock")
+    private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
+
     private final Bundle mGuestRestrictions = new Bundle();
 
     /**
@@ -727,8 +738,6 @@
             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.
@@ -738,15 +747,22 @@
             mBaseUserRestrictions.put(userId, newRestrictions);
         }
 
-        mCachedEffectiveUserRestrictions.put(
-                userId, computeEffectiveUserRestrictionsRL(userId));
+        final Bundle effective = computeEffectiveUserRestrictionsRL(userId);
 
-        applyUserRestrictionsRL(userId, mBaseUserRestrictions.get(userId), prevRestrictions);
+        mCachedEffectiveUserRestrictions.put(userId, effective);
+
+        applyUserRestrictionsRL(userId, effective);
     }
 
     @GuardedBy("mRestrictionsLock")
-    private void applyUserRestrictionsRL(int userId,
-            Bundle newRestrictions, Bundle prevRestrictions) {
+    private void applyUserRestrictionsRL(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);
@@ -756,7 +772,10 @@
             Binder.restoreCallingIdentity(token);
         }
 
-        // TODO Move the code from DPMS.setUserRestriction().
+        UserRestrictionsUtils.applyUserRestrictions(
+                mContext, userId, newRestrictions, prevRestrictions);
+
+        mAppliedUserRestrictions.put(userId, new Bundle(newRestrictions));
     }
 
     @GuardedBy("mRestrictionsLock")
@@ -767,9 +786,8 @@
     @GuardedBy("mRestrictionsLock")
     private void updateEffectiveUserRestrictionsForAllUsersRL() {
         // 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() {
@@ -2150,6 +2168,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) {
@@ -2273,4 +2330,22 @@
             }
         }
     }
+
+    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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fab8ee5..d6f807e 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;
@@ -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();
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..dbf13fe 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;
 
@@ -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..95e2391 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.
@@ -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..230e81b 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;
@@ -2468,6 +2468,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,
@@ -4607,6 +4655,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
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5bc329e..c1fa78a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1999,4 +1999,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..8dddbd1 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 {
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..1602c12 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,14 @@
                 }
             }
         }
+        if (removed) {
+            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+                synchronized (DevicePolicyManagerService.this) {
+                    mUserManagerInternal.updateEffectiveUserRestrictionsRL(
+                            userHandle);
+                }
+            }
+        }
     }
 
     /**
@@ -1682,7 +1690,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 +1709,6 @@
                                 policy.mAdminList.remove(admin);
                                 policy.mAdminMap.remove(adminReceiver);
                                 validatePasswordOwnerLocked(policy);
-                                syncDeviceCapabilitiesLocked(policy);
                                 if (doProxyCleanup) {
                                     resetGlobalProxyLocked(getUserData(userHandle));
                                 }
@@ -1709,6 +1716,12 @@
                                 updateMaximumTimeToLockLocked(policy);
                                 policy.mRemovingAdmins.remove(adminReceiver);
                             }
+                            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+                                synchronized (DevicePolicyManagerService.this) {
+                                    mUserManagerInternal.updateEffectiveUserRestrictionsRL(
+                                            userHandle);
+                                }
+                            }
                         }
                     });
         }
@@ -2022,7 +2035,6 @@
         }
 
         validatePasswordOwnerLocked(policy);
-        syncDeviceCapabilitiesLocked(policy);
         updateMaximumTimeToLockLocked(policy);
         updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
         if (policy.mStatusBarDisabled) {
@@ -2089,31 +2101,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 +4316,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 +4332,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.updateEffectiveUserRestrictionsForAllUsersRL();
+                } else {
+                    mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle);
+                }
+            }
         }
     }
 
@@ -4370,7 +4359,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 +4399,6 @@
                 ap.disabledKeyguardFeatures = which;
                 saveSettingsLocked(userHandle);
             }
-            syncDeviceCapabilitiesLocked(getUserData(userHandle));
         }
     }
 
@@ -5036,7 +5030,6 @@
                     DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES);
             ap.trustAgentInfos.put(agent.flattenToString(), new TrustAgentInfo(args));
             saveSettingsLocked(userHandle);
-            syncDeviceCapabilitiesLocked(getUserData(userHandle));
         }
     }
 
@@ -5602,6 +5595,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 +5606,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 +5618,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 +5626,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();
                     } else {
                         mUserManagerInternal.updateEffectiveUserRestrictionsRL(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 +6381,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 +6393,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/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/aapt2/Android.mk b/tools/aapt2/Android.mk
index 275476c..a41d2d7 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -45,9 +45,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 \
@@ -76,7 +78,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 \
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..0c7a4d5 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -308,6 +308,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 +324,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;
@@ -464,6 +467,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 +581,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 +624,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 +658,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 +733,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 +799,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 +811,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 +831,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 +849,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 +871,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 +879,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 +907,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 +915,7 @@
             }
 
             if (plural->values[index]) {
-                mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                mDiag->error(DiagMessage(itemSource)
                              << "duplicate quantity '" << trimmedQuantity << "'");
                 error = true;
                 continue;
@@ -918,11 +925,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 +950,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/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/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..b1987f1 100644
--- a/tools/aapt2/XmlDom.h
+++ b/tools/aapt2/XmlDom.h
@@ -132,6 +132,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/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
new file mode 100644
index 0000000..16440bc
--- /dev/null
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -0,0 +1,81 @@
+/*
+ * 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 StringPiece16& line) {
+    static const std::string sDeprecated = "@deprecated";
+    static const std::string sSystemApi = "@SystemApi";
+
+    if (line.empty()) {
+        return;
+    }
+
+    std::string comment = util::utf16ToUtf8(line);
+
+    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')) {
+        appendCommentLine(util::trimWhitespace(line));
+    }
+}
+
+std::string AnnotationProcessor::buildComment() {
+    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..b472109
--- /dev/null
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -0,0 +1,93 @@
+/*
+ * 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);
+
+    /**
+     * 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 StringPiece16& line);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_JAVA_ANNOTATIONPROCESSOR_H */
diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
similarity index 98%
rename from tools/aapt2/JavaClassGenerator.cpp
rename to tools/aapt2/java/JavaClassGenerator.cpp
index cdf1b6a..0175489 100644
--- a/tools/aapt2/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-#include "JavaClassGenerator.h"
 #include "NameMangler.h"
 #include "Resource.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
+#include "java/AnnotationProcessor.h"
+#include "java/JavaClassGenerator.h"
 #include "util/StringPiece.h"
 
 #include <algorithm>
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 99%
rename from tools/aapt2/JavaClassGenerator_test.cpp
rename to tools/aapt2/java/JavaClassGenerator_test.cpp
index cc5e981..3625f9c 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"
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
new file mode 100644
index 0000000..c12da64
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -0,0 +1,133 @@
+/*
+ * 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 {
+
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
+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(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 99%
rename from tools/aapt2/ProguardRules.cpp
rename to tools/aapt2/java/ProguardRules.cpp
index 7f4dc91..7683f27 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>
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..77918ac 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -17,15 +17,16 @@
 #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/TableMerger.h"
 #include "process/IResourceTableConsumer.h"
@@ -354,6 +355,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;
@@ -548,6 +579,12 @@
                     error = true;
                 }
 
+                if (mOptions.generateJavaClassPath) {
+                    if (!writeManifestJavaFile(manifestXml.get())) {
+                        error = true;
+                    }
+                }
+
                 if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
                                 archiveWriter.get())) {
                     error = true;
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..30c6091 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -678,8 +678,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/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.
     }