Merge "Remove scheduling animation which is no longer needed"
diff --git a/api/current.txt b/api/current.txt
index 55f8751..96fbd31 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6899,6 +6899,7 @@
     method public int getClipGrantFlags();
     method public android.os.PersistableBundle getExtras();
     method public int getJobId();
+    method public android.net.Network getNetwork();
     method public android.os.Bundle getTransientExtras();
     method public java.lang.String[] getTriggeredContentAuthorities();
     method public android.net.Uri[] getTriggeredContentUris();
@@ -52074,7 +52075,6 @@
     method public android.graphics.Typeface getTypeface();
     method public android.text.style.URLSpan[] getUrls();
     method public boolean hasSelection();
-    method public void invalidate(int, int, int, int);
     method public boolean isAllCaps();
     method public boolean isCursorVisible();
     method public boolean isElegantTextHeight();
diff --git a/api/system-current.txt b/api/system-current.txt
index c6a6fdd..4f2c2ba 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -7341,6 +7341,7 @@
     method public int getClipGrantFlags();
     method public android.os.PersistableBundle getExtras();
     method public int getJobId();
+    method public android.net.Network getNetwork();
     method public android.os.Bundle getTransientExtras();
     method public java.lang.String[] getTriggeredContentAuthorities();
     method public android.net.Uri[] getTriggeredContentUris();
@@ -56178,7 +56179,6 @@
     method public android.graphics.Typeface getTypeface();
     method public android.text.style.URLSpan[] getUrls();
     method public boolean hasSelection();
-    method public void invalidate(int, int, int, int);
     method public boolean isAllCaps();
     method public boolean isCursorVisible();
     method public boolean isElegantTextHeight();
diff --git a/api/test-current.txt b/api/test-current.txt
index 5e55bc3..dd0e999 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6970,6 +6970,7 @@
     method public int getClipGrantFlags();
     method public android.os.PersistableBundle getExtras();
     method public int getJobId();
+    method public android.net.Network getNetwork();
     method public android.os.Bundle getTransientExtras();
     method public java.lang.String[] getTriggeredContentAuthorities();
     method public android.net.Uri[] getTriggeredContentUris();
@@ -52683,7 +52684,6 @@
     method public android.graphics.Typeface getTypeface();
     method public android.text.style.URLSpan[] getUrls();
     method public boolean hasSelection();
-    method public void invalidate(int, int, int, int);
     method public boolean isAllCaps();
     method public boolean isCursorVisible();
     method public boolean isElegantTextHeight();
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 60ec8a9..29433f3 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -116,9 +116,8 @@
     }
 
     public int run(String[] args) throws RemoteException {
-        boolean validCommand = false;
         if (args.length < 1) {
-            return showUsage();
+            return runShellCommand("package", mArgs);
         }
         mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE));
         mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE));
@@ -134,18 +133,6 @@
         String op = args[0];
         mNextArg = 1;
 
-        if ("list".equals(op)) {
-            return runList();
-        }
-
-        if ("path".equals(op)) {
-            return runPath();
-        }
-
-        if ("dump".equals(op)) {
-            return runDump();
-        }
-
         if ("install".equals(op)) {
             return runInstall();
         }
@@ -166,134 +153,7 @@
             return runInstallAbandon();
         }
 
-        if ("set-installer".equals(op)) {
-            return runSetInstaller();
-        }
-
-        if ("uninstall".equals(op)) {
-            return runUninstall();
-        }
-
-        if ("clear".equals(op)) {
-            return runClear();
-        }
-
-        if ("enable".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
-        }
-
-        if ("disable".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
-        }
-
-        if ("disable-user".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
-        }
-
-        if ("disable-until-used".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
-        }
-
-        if ("default-state".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
-        }
-
-        if ("hide".equals(op)) {
-            return runSetHiddenSetting(true);
-        }
-
-        if ("unhide".equals(op)) {
-            return runSetHiddenSetting(false);
-        }
-
-        if ("grant".equals(op)) {
-            return runGrantRevokePermission(true);
-        }
-
-        if ("revoke".equals(op)) {
-            return runGrantRevokePermission(false);
-        }
-
-        if ("reset-permissions".equals(op)) {
-            return runResetPermissions();
-        }
-
-        if ("set-permission-enforced".equals(op)) {
-            return runSetPermissionEnforced();
-        }
-
-        if ("set-app-link".equals(op)) {
-            return runSetAppLink();
-        }
-
-        if ("get-app-link".equals(op)) {
-            return runGetAppLink();
-        }
-
-        if ("set-install-location".equals(op)) {
-            return runSetInstallLocation();
-        }
-
-        if ("get-install-location".equals(op)) {
-            return runGetInstallLocation();
-        }
-
-        if ("trim-caches".equals(op)) {
-            return runTrimCaches();
-        }
-
-        if ("create-user".equals(op)) {
-            return runCreateUser();
-        }
-
-        if ("remove-user".equals(op)) {
-            return runRemoveUser();
-        }
-
-        if ("get-max-users".equals(op)) {
-            return runGetMaxUsers();
-        }
-
-        if ("force-dex-opt".equals(op)) {
-            return runForceDexOpt();
-        }
-
-        if ("move-package".equals(op)) {
-            return runMovePackage();
-        }
-
-        if ("move-primary-storage".equals(op)) {
-            return runMovePrimaryStorage();
-        }
-
-        if ("set-user-restriction".equals(op)) {
-            return runSetUserRestriction();
-        }
-
-        try {
-            if (args.length == 1) {
-                if (args[0].equalsIgnoreCase("-l")) {
-                    validCommand = true;
-                    return runShellCommand("package", new String[] { "list", "package" });
-                } else if (args[0].equalsIgnoreCase("-lf")) {
-                    validCommand = true;
-                    return runShellCommand("package", new String[] { "list", "package", "-f" });
-                }
-            } else if (args.length == 2) {
-                if (args[0].equalsIgnoreCase("-p")) {
-                    validCommand = true;
-                    return displayPackageFilePath(args[1], UserHandle.USER_SYSTEM);
-                }
-            }
-            return 1;
-        } finally {
-            if (validCommand == false) {
-                if (op != null) {
-                    System.err.println("Error: unknown command '" + op + "'");
-                }
-                showUsage();
-            }
-        }
+        return runShellCommand("package", mArgs);
     }
 
     static final class MyShellCallback extends ShellCallback {
@@ -704,59 +564,6 @@
         }
     }
 
-    /**
-     * Execute the list sub-command.
-     *
-     * pm list [package | packages]
-     * pm list permission-groups
-     * pm list permissions
-     * pm list features
-     * pm list libraries
-     * pm list instrumentation
-     */
-    private int runList() {
-        final String type = nextArg();
-        if ("users".equals(type)) {
-            return runShellCommand("user", new String[] { "list" });
-        }
-        return runShellCommand("package", mArgs);
-    }
-
-    private int runUninstall() {
-        return runShellCommand("package", mArgs);
-    }
-
-    private int runPath() {
-        int userId = UserHandle.USER_SYSTEM;
-        String option = nextOption();
-        if (option != null && option.equals("--user")) {
-            String optionData = nextOptionData();
-            if (optionData == null || !isNumber(optionData)) {
-                System.err.println("Error: no USER_ID specified");
-                return showUsage();
-            } else {
-                userId = Integer.parseInt(optionData);
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified");
-            return 1;
-        }
-        return displayPackageFilePath(pkg, userId);
-    }
-
-    private int runDump() {
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified");
-            return 1;
-        }
-        ActivityManager.dumpPackageStateStatic(FileDescriptor.out, pkg);
-        return 0;
-    }
-
     class LocalPackageInstallObserver extends PackageInstallObserver {
         boolean finished;
         int result;
@@ -779,477 +586,6 @@
         }
     }
 
-    // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined}
-    private int runSetAppLink() {
-        int userId = UserHandle.USER_SYSTEM;
-
-        String opt;
-        while ((opt = nextOption()) != null) {
-            if (opt.equals("--user")) {
-                userId = Integer.parseInt(nextOptionData());
-                if (userId < 0) {
-                    System.err.println("Error: user must be >= 0");
-                    return 1;
-                }
-            } else {
-                System.err.println("Error: unknown option: " + opt);
-                return showUsage();
-            }
-        }
-
-        // Package name to act on; required
-        final String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
-            return showUsage();
-        }
-
-        // State to apply; {always|ask|never|undefined}, required
-        final String modeString = nextArg();
-        if (modeString == null) {
-            System.err.println("Error: no app link state specified.");
-            return showUsage();
-        }
-
-        final int newMode;
-        switch (modeString.toLowerCase()) {
-            case "undefined":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-                break;
-
-            case "always":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-                break;
-
-            case "ask":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-                break;
-
-            case "always-ask":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
-                break;
-
-            case "never":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-                break;
-
-            default:
-                System.err.println("Error: unknown app link state '" + modeString + "'");
-                return 1;
-        }
-
-        try {
-            final PackageInfo info = mPm.getPackageInfo(pkg, 0, userId);
-            if (info == null) {
-                System.err.println("Error: package " + pkg + " not found.");
-                return 1;
-            }
-
-            if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
-                System.err.println("Error: package " + pkg + " does not handle web links.");
-                return 1;
-            }
-
-            if (!mPm.updateIntentVerificationStatus(pkg, newMode, userId)) {
-                System.err.println("Error: unable to update app link status for " + pkg);
-                return 1;
-            }
-        } catch (Exception e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-
-        return 0;
-    }
-
-    // pm get-app-link [--user USER_ID] PACKAGE
-    private int runGetAppLink() {
-        int userId = UserHandle.USER_SYSTEM;
-
-        String opt;
-        while ((opt = nextOption()) != null) {
-            if (opt.equals("--user")) {
-                userId = Integer.parseInt(nextOptionData());
-                if (userId < 0) {
-                    System.err.println("Error: user must be >= 0");
-                    return 1;
-                }
-            } else {
-                System.err.println("Error: unknown option: " + opt);
-                return showUsage();
-            }
-        }
-
-        // Package name to act on; required
-        final String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
-            return showUsage();
-        }
-
-        try {
-            final PackageInfo info = mPm.getPackageInfo(pkg, 0, userId);
-            if (info == null) {
-                System.err.println("Error: package " + pkg + " not found.");
-                return 1;
-            }
-
-            if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
-                System.err.println("Error: package " + pkg + " does not handle web links.");
-                return 1;
-            }
-
-            System.out.println(linkStateToString(mPm.getIntentVerificationStatus(pkg, userId)));
-        } catch (Exception e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-
-        return 0;
-    }
-
-    private String linkStateToString(int state) {
-        switch (state) {
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask";
-        }
-        return "Unknown link state: " + state;
-    }
-
-    private int runSetInstallLocation() {
-        int loc;
-
-        String arg = nextArg();
-        if (arg == null) {
-            System.err.println("Error: no install location specified.");
-            return 1;
-        }
-        try {
-            loc = Integer.parseInt(arg);
-        } catch (NumberFormatException e) {
-            System.err.println("Error: install location has to be a number.");
-            return 1;
-        }
-        try {
-            if (!mPm.setInstallLocation(loc)) {
-                System.err.println("Error: install location has to be a number.");
-                return 1;
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    private int runGetInstallLocation() {
-        try {
-            int loc = mPm.getInstallLocation();
-            String locStr = "invalid";
-            if (loc == PackageHelper.APP_INSTALL_AUTO) {
-                locStr = "auto";
-            } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) {
-                locStr = "internal";
-            } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) {
-                locStr = "external";
-            }
-            System.out.println(loc + "[" + locStr + "]");
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    private int runSetInstaller() throws RemoteException {
-        final String targetPackage = nextArg();
-        final String installerPackageName = nextArg();
-
-        if (targetPackage == null || installerPackageName == null) {
-            throw new IllegalArgumentException(
-                    "must provide both target and installer package names");
-        }
-
-        mPm.setInstallerPackageName(targetPackage, installerPackageName);
-        System.out.println("Success");
-        return 0;
-    }
-
-    public int runCreateUser() {
-        String name;
-        int userId = -1;
-        int flags = 0;
-        String opt;
-        while ((opt = nextOption()) != null) {
-            if ("--profileOf".equals(opt)) {
-                String optionData = nextOptionData();
-                if (optionData == null || !isNumber(optionData)) {
-                    System.err.println("Error: no USER_ID specified");
-                    return showUsage();
-                } else {
-                    userId = Integer.parseInt(optionData);
-                }
-            } else if ("--managed".equals(opt)) {
-                flags |= UserInfo.FLAG_MANAGED_PROFILE;
-            } else if ("--restricted".equals(opt)) {
-                flags |= UserInfo.FLAG_RESTRICTED;
-            } else if ("--ephemeral".equals(opt)) {
-                flags |= UserInfo.FLAG_EPHEMERAL;
-            } else if ("--guest".equals(opt)) {
-                flags |= UserInfo.FLAG_GUEST;
-            } else if ("--demo".equals(opt)) {
-                flags |= UserInfo.FLAG_DEMO;
-            } else {
-                System.err.println("Error: unknown option " + opt);
-                return showUsage();
-            }
-        }
-        String arg = nextArg();
-        if (arg == null) {
-            System.err.println("Error: no user name specified.");
-            return 1;
-        }
-        name = arg;
-        try {
-            UserInfo info;
-            if ((flags & UserInfo.FLAG_RESTRICTED) != 0) {
-                // In non-split user mode, userId can only be SYSTEM
-                int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM;
-                info = mUm.createRestrictedProfile(name, parentUserId);
-                mAm.addSharedAccountsFromParentUser(parentUserId, userId,
-                        (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell");
-            } else if (userId < 0) {
-                info = mUm.createUser(name, flags);
-            } else {
-                info = mUm.createProfileForUser(name, flags, userId, null);
-            }
-
-            if (info != null) {
-                System.out.println("Success: created user id " + info.id);
-                return 0;
-            } else {
-                System.err.println("Error: couldn't create User.");
-                return 1;
-            }
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    public int runRemoveUser() {
-        int userId;
-        String arg = nextArg();
-        if (arg == null) {
-            System.err.println("Error: no user id specified.");
-            return 1;
-        }
-        try {
-            userId = Integer.parseInt(arg);
-        } catch (NumberFormatException e) {
-            System.err.println("Error: user id '" + arg + "' is not a number.");
-            return 1;
-        }
-        try {
-            if (mUm.removeUser(userId)) {
-                System.out.println("Success: removed user");
-                return 0;
-            } else {
-                System.err.println("Error: couldn't remove user id " + userId);
-                return 1;
-            }
-        } 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;
-    }
-
-    public int runForceDexOpt() {
-        final String packageName = nextArg();
-        try {
-            mPm.forceDexOpt(packageName);
-            return 0;
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
-    }
-
-    public int runMovePackage() {
-        final String packageName = nextArg();
-        String volumeUuid = nextArg();
-        if ("internal".equals(volumeUuid)) {
-            volumeUuid = null;
-        }
-
-        try {
-            final int moveId = mPm.movePackage(packageName, volumeUuid);
-
-            int status = mPm.getMoveStatus(moveId);
-            while (!PackageManager.isMoveStatusFinished(status)) {
-                SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
-                status = mPm.getMoveStatus(moveId);
-            }
-
-            if (status == PackageManager.MOVE_SUCCEEDED) {
-                System.out.println("Success");
-                return 0;
-            } else {
-                System.err.println("Failure [" + status + "]");
-                return 1;
-            }
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
-    }
-
-    public int runMovePrimaryStorage() {
-        String volumeUuid = nextArg();
-        if ("internal".equals(volumeUuid)) {
-            volumeUuid = null;
-        }
-
-        try {
-            final int moveId = mPm.movePrimaryStorage(volumeUuid);
-
-            int status = mPm.getMoveStatus(moveId);
-            while (!PackageManager.isMoveStatusFinished(status)) {
-                SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
-                status = mPm.getMoveStatus(moveId);
-            }
-
-            if (status == PackageManager.MOVE_SUCCEEDED) {
-                System.out.println("Success");
-                return 0;
-            } else {
-                System.err.println("Failure [" + status + "]");
-                return 1;
-            }
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
-    }
-
-    public int runSetUserRestriction() {
-        int userId = UserHandle.USER_SYSTEM;
-        String opt = nextOption();
-        if (opt != null && "--user".equals(opt)) {
-            String arg = nextArg();
-            if (arg == null || !isNumber(arg)) {
-                System.err.println("Error: valid userId not specified");
-                return 1;
-            }
-            userId = Integer.parseInt(arg);
-        }
-
-        String restriction = nextArg();
-        String arg = nextArg();
-        boolean value;
-        if ("1".equals(arg)) {
-            value = true;
-        } else if ("0".equals(arg)) {
-            value = false;
-        } else {
-            System.err.println("Error: valid value not specified");
-            return 1;
-        }
-        try {
-            mUm.setUserRestriction(restriction, value, userId);
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            return 1;
-        }
-    }
-
-    static class ClearDataObserver extends IPackageDataObserver.Stub {
-        boolean finished;
-        boolean result;
-
-        @Override
-        public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
-            synchronized (this) {
-                finished = true;
-                result = succeeded;
-                notifyAll();
-            }
-        }
-    }
-
-    private int runClear() {
-        int userId = UserHandle.USER_SYSTEM;
-        String option = nextOption();
-        if (option != null && option.equals("--user")) {
-            String optionData = nextOptionData();
-            if (optionData == null || !isNumber(optionData)) {
-                System.err.println("Error: no USER_ID specified");
-                return showUsage();
-            } else {
-                userId = Integer.parseInt(optionData);
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified");
-            return showUsage();
-        }
-
-        ClearDataObserver obs = new ClearDataObserver();
-        try {
-            ActivityManager.getService().clearApplicationUserData(pkg, obs, userId);
-            synchronized (obs) {
-                while (!obs.finished) {
-                    try {
-                        obs.wait();
-                    } catch (InterruptedException e) {
-                    }
-                }
-            }
-
-            if (obs.result) {
-                System.out.println("Success");
-                return 0;
-            } else {
-                System.err.println("Failed");
-                return 1;
-            }
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    private static String enabledSettingToString(int state) {
-        switch (state) {
-            case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
-                return "default";
-            case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
-                return "enabled";
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
-                return "disabled";
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
-                return "disabled-user";
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
-                return "disabled-until-used";
-        }
-        return "unknown";
-    }
-
     private static boolean isNumber(String s) {
         try {
             Integer.parseInt(s);
@@ -1259,169 +595,6 @@
         return true;
     }
 
-    private int runSetEnabledSetting(int state) {
-        int userId = UserHandle.USER_SYSTEM;
-        String option = nextOption();
-        if (option != null && option.equals("--user")) {
-            String optionData = nextOptionData();
-            if (optionData == null || !isNumber(optionData)) {
-                System.err.println("Error: no USER_ID specified");
-                return showUsage();
-            } else {
-                userId = Integer.parseInt(optionData);
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package or component specified");
-            return showUsage();
-        }
-        ComponentName cn = ComponentName.unflattenFromString(pkg);
-        if (cn == null) {
-            try {
-                mPm.setApplicationEnabledSetting(pkg, state, 0, userId,
-                        "shell:" + android.os.Process.myUid());
-                System.out.println("Package " + pkg + " new state: "
-                        + enabledSettingToString(
-                        mPm.getApplicationEnabledSetting(pkg, userId)));
-                return 0;
-            } catch (RemoteException e) {
-                System.err.println(e.toString());
-                System.err.println(PM_NOT_RUNNING_ERR);
-                return 1;
-            }
-        } else {
-            try {
-                mPm.setComponentEnabledSetting(cn, state, 0, userId);
-                System.out.println("Component " + cn.toShortString() + " new state: "
-                        + enabledSettingToString(
-                        mPm.getComponentEnabledSetting(cn, userId)));
-                return 0;
-            } catch (RemoteException e) {
-                System.err.println(e.toString());
-                System.err.println(PM_NOT_RUNNING_ERR);
-                return 1;
-            }
-        }
-    }
-
-    private int runSetHiddenSetting(boolean state) {
-        int userId = UserHandle.USER_SYSTEM;
-        String option = nextOption();
-        if (option != null && option.equals("--user")) {
-            String optionData = nextOptionData();
-            if (optionData == null || !isNumber(optionData)) {
-                System.err.println("Error: no USER_ID specified");
-                return showUsage();
-            } else {
-                userId = Integer.parseInt(optionData);
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package or component specified");
-            return showUsage();
-        }
-        try {
-            mPm.setApplicationHiddenSettingAsUser(pkg, state, userId);
-            System.out.println("Package " + pkg + " new hidden state: "
-                    + mPm.getApplicationHiddenSettingAsUser(pkg, userId));
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    private int runGrantRevokePermission(boolean grant) {
-        int userId = UserHandle.USER_SYSTEM;
-
-        String opt = null;
-        while ((opt = nextOption()) != null) {
-            if (opt.equals("--user")) {
-                userId = Integer.parseInt(nextArg());
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified");
-            return showUsage();
-        }
-        String perm = nextArg();
-        if (perm == null) {
-            System.err.println("Error: no permission specified");
-            return showUsage();
-        }
-
-        try {
-            if (grant) {
-                mPm.grantRuntimePermission(pkg, perm, userId);
-            } else {
-                mPm.revokeRuntimePermission(pkg, perm, userId);
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        } catch (IllegalArgumentException e) {
-            System.err.println("Bad argument: " + e.toString());
-            return showUsage();
-        } catch (SecurityException e) {
-            System.err.println("Operation not allowed: " + e.toString());
-            return 1;
-        }
-    }
-
-    private int runResetPermissions() {
-        try {
-            mPm.resetRuntimePermissions();
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        } catch (IllegalArgumentException e) {
-            System.err.println("Bad argument: " + e.toString());
-            return showUsage();
-        } catch (SecurityException e) {
-            System.err.println("Operation not allowed: " + e.toString());
-            return 1;
-        }
-    }
-
-    private int runSetPermissionEnforced() {
-        final String permission = nextArg();
-        if (permission == null) {
-            System.err.println("Error: no permission specified");
-            return showUsage();
-        }
-        final String enforcedRaw = nextArg();
-        if (enforcedRaw == null) {
-            System.err.println("Error: no enforcement specified");
-            return showUsage();
-        }
-        final boolean enforced = Boolean.parseBoolean(enforcedRaw);
-        try {
-            mPm.setPermissionEnforced(permission, enforced);
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        } catch (IllegalArgumentException e) {
-            System.err.println("Bad argument: " + e.toString());
-            return showUsage();
-        } catch (SecurityException e) {
-            System.err.println("Operation not allowed: " + e.toString());
-            return 1;
-        }
-    }
-
     static class ClearCacheObserver extends IPackageDataObserver.Stub {
         boolean finished;
         boolean result;
@@ -1437,62 +610,17 @@
 
     }
 
-    private int runTrimCaches() {
-        String size = nextArg();
-        if (size == null) {
-            System.err.println("Error: no size specified");
-            return showUsage();
-        }
-        long multiplier = 1;
-        int len = size.length();
-        char c = size.charAt(len - 1);
-        if (c < '0' || c > '9') {
-            if (c == 'K' || c == 'k') {
-                multiplier = 1024L;
-            } else if (c == 'M' || c == 'm') {
-                multiplier = 1024L*1024L;
-            } else if (c == 'G' || c == 'g') {
-                multiplier = 1024L*1024L*1024L;
-            } else {
-                System.err.println("Invalid suffix: " + c);
-                return showUsage();
+    static class ClearDataObserver extends IPackageDataObserver.Stub {
+        boolean finished;
+        boolean result;
+
+        @Override
+        public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
+            synchronized (this) {
+                finished = true;
+                result = succeeded;
+                notifyAll();
             }
-            size = size.substring(0, len-1);
-        }
-        long sizeVal;
-        try {
-            sizeVal = Long.parseLong(size) * multiplier;
-        } catch (NumberFormatException e) {
-            System.err.println("Error: expected number at: " + size);
-            return showUsage();
-        }
-        String volumeUuid = nextArg();
-        if ("internal".equals(volumeUuid)) {
-            volumeUuid = null;
-        }
-        ClearDataObserver obs = new ClearDataObserver();
-        try {
-            mPm.freeStorageAndNotify(volumeUuid, sizeVal,
-                    StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED, obs);
-            synchronized (obs) {
-                while (!obs.finished) {
-                    try {
-                        obs.wait();
-                    } catch (InterruptedException e) {
-                    }
-                }
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        } catch (IllegalArgumentException e) {
-            System.err.println("Bad argument: " + e.toString());
-            return showUsage();
-        } catch (SecurityException e) {
-            System.err.println("Operation not allowed: " + e.toString());
-            return 1;
         }
     }
 
diff --git a/cmds/statsd/src/metrics/CountAnomalyTracker.h b/cmds/statsd/src/metrics/CountAnomalyTracker.h
index 13c1ccd..79c47d2a 100644
--- a/cmds/statsd/src/metrics/CountAnomalyTracker.h
+++ b/cmds/statsd/src/metrics/CountAnomalyTracker.h
@@ -26,6 +26,8 @@
 namespace os {
 namespace statsd {
 
+// TODO: Can probably be used for Count, Value, and Gauge. If so, rename to ValueAnomalyTracker.
+// (caveat: currently, the value cannot be negative. Probably fine for P.)
 class CountAnomalyTracker {
 public:
     CountAnomalyTracker(const Alert& alert);
diff --git a/cmds/statsd/src/stats_events.proto b/cmds/statsd/src/stats_events.proto
index 3962140..74ee332 100644
--- a/cmds/statsd/src/stats_events.proto
+++ b/cmds/statsd/src/stats_events.proto
@@ -46,6 +46,7 @@
         ScheduledJobStateChanged scheduled_job_state_changed = 8;
         ScreenBrightnessChanged screen_brightness_changed = 9;
         // 10-20 are temporarily reserved for wakelocks etc.
+        WakelockStateChanged wakelock_state_changed = 10;
         UidWakelockStateChanged uid_wakelock_state_changed = 11;
         LongPartialWakelockStateChanged long_partial_wakelock_state_changed = 12;
         BatterySaverModeStateChanged battery_saver_mode_state_changed = 21;
@@ -57,6 +58,18 @@
         UidProcessStateChanged uid_process_state_changed = 27;
         ProcessLifeCycleStateChanged process_life_cycle_state_changed = 28;
         ScreenStateChanged screen_state_changed = 29;
+        BatteryLevelChanged battery_level_changed = 30;
+        ChargingStateChanged charging_state_changed = 31;
+        PluggedStateChanged plugged_state_changed = 32;
+        DeviceTemperatureReported device_temperature_reported = 33;
+        DeviceOnStatusChanged device_on_status_changed = 34;
+        WakeupAlarmOccurred wakeup_alarm_occurred = 35;
+        KernelWakeupReported kernel_wakeup_reported = 36;
+        WifiLockStateChanged wifi_lock_state_changed = 37;
+        WifiSignalStrengthChanged wifi_signal_strength_changed = 38;
+        WifiScanStateChanged wifi_scan_state_changed = 39;
+        PhoneSignalStrengthChanged phone_signal_strength_changed = 40;
+
         // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
     }
 }
@@ -356,7 +369,7 @@
  * Logged from:
  *   TODO
  */
-message WakelockChanged {
+message WakelockStateChanged {
     // TODO: Add attribution instead of uid.
     optional int32 uid = 1;
 
@@ -460,4 +473,176 @@
 message ScreenBrightnessChanged {
     // Screen brightness level. Should be in [-1, 255] according to PowerManager.java.
     optional int32 level = 1;
+}
+
+/**
+ * Logs battery level (percent full, from 0 to 100).
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message BatteryLevelChanged {
+    // Battery level. Should be in [0, 100].
+    optional int32 battery_level = 1;
+}
+
+/**
+ * Logs change in charging status of the device.
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message ChargingStateChanged {
+    // TODO: Link directly to BatteryManager.java's constants (via a proto).
+    enum State {
+        BATTERY_STATUS_UNKNOWN = 1;
+        BATTERY_STATUS_CHARGING = 2;
+        BATTERY_STATUS_DISCHARGING = 3;
+        BATTERY_STATUS_NOT_CHARGING = 4;
+        BATTERY_STATUS_FULL = 5;
+    }
+    optional State charging_state = 1;
+}
+
+/**
+ * Logs whether the device is plugged in, and what power source it is using.
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message PluggedStateChanged {
+    // TODO: Link directly to BatteryManager.java's constants (via a proto).
+    enum State {
+        // Note that NONE is not in BatteryManager.java's constants.
+        BATTERY_PLUGGED_NONE = 0;
+        // Power source is an AC charger.
+        BATTERY_PLUGGED_AC = 1;
+        // Power source is a USB port.
+        BATTERY_PLUGGED_USB = 2;
+        // Power source is wireless.
+        BATTERY_PLUGGED_WIRELESS = 4;
+    }
+    optional State plugged_state = 1;
+}
+
+/**
+ * Logs the temperature of the device, in tenths of a degree Celsius.
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message DeviceTemperatureReported {
+    // Temperature in tenths of a degree C.
+    optional int32 temperature = 1;
+}
+
+// TODO: Define this more precisely.
+// TODO: Log the ON state somewhere. It isn't currently logged anywhere.
+/**
+ * Logs when the device turns off or on.
+ *
+ * Logged from:
+  *   frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ */
+message DeviceOnStatusChanged {
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 1;
+}
+
+/**
+ * Logs when an app's wakeup alarm fires.
+ *
+ * Logged from:
+  *   frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ */
+message WakeupAlarmOccurred {
+    // TODO: Add attribution instead of uid?
+    optional int32 uid = 1;
+}
+
+/**
+ * Logs kernel wakeup reasons and aborts.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message KernelWakeupReported {
+    // Name of the kernel wakeup reason (or abort).
+    optional string wakeup_reason_name = 1;
+
+    // Duration (in microseconds) for the wake-up interrupt to be serviced.
+    optional int64 duration_usec = 2;
+}
+
+/**
+ * Logs wifi locks held by an app.
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message WifiLockStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs wifi signal strength changes.
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message WifiSignalStrengthChanged {
+    // TODO: Reference the actual telephony/java/android/telephony/SignalStrength.java states.
+    enum SignalStrength {
+        SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0;
+        SIGNAL_STRENGTH_POOR = 1;
+        SIGNAL_STRENGTH_MODERATE = 2;
+        SIGNAL_STRENGTH_GOOD = 3;
+        SIGNAL_STRENGTH_GREAT = 4;
+    }
+    optional SignalStrength signal_strength = 1;
+}
+
+/**
+ * Logs wifi scans performed by an app.
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message WifiScanStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs phone signal strength changes.
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message PhoneSignalStrengthChanged {
+    // TODO: Reference the actual telephony/java/android/telephony/SignalStrength.java states.
+    enum SignalStrength {
+        SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0;
+        SIGNAL_STRENGTH_POOR = 1;
+        SIGNAL_STRENGTH_MODERATE = 2;
+        SIGNAL_STRENGTH_GOOD = 3;
+        SIGNAL_STRENGTH_GREAT = 4;
+    }
+    optional SignalStrength signal_strength = 1;
 }
\ No newline at end of file
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index fc4c8d7..280b1e80 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -46,6 +46,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.BatteryStats;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -1596,6 +1597,9 @@
     public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags)
             throws SecurityException {
         try {
+            if (maxNum < 0) {
+                throw new IllegalArgumentException("The requested number of tasks should be >= 0");
+            }
             return getService().getRecentTasks(maxNum, flags, UserHandle.myUserId()).getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -3882,21 +3886,36 @@
         IBinder service = ServiceManager.checkService(name);
         if (service == null) {
             pw.println("  (Service not found)");
+            pw.flush();
             return;
         }
-        TransferPipe tp = null;
-        try {
-            pw.flush();
-            tp = new TransferPipe();
-            tp.setBufferPrefix("  ");
-            service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
-            tp.go(fd, 10000);
-        } catch (Throwable e) {
-            if (tp != null) {
-                tp.kill();
+        pw.flush();
+        if (service instanceof Binder) {
+            // If this is a local object, it doesn't make sense to do an async dump with it,
+            // just directly dump.
+            try {
+                service.dump(fd, args);
+            } catch (Throwable e) {
+                pw.println("Failure dumping service:");
+                e.printStackTrace(pw);
+                pw.flush();
             }
-            pw.println("Failure dumping service:");
-            e.printStackTrace(pw);
+        } else {
+            // Otherwise, it is remote, do the dump asynchronously to avoid blocking.
+            TransferPipe tp = null;
+            try {
+                pw.flush();
+                tp = new TransferPipe();
+                tp.setBufferPrefix("  ");
+                service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
+                tp.go(fd, 10000);
+            } catch (Throwable e) {
+                if (tp != null) {
+                    tp.kill();
+                }
+                pw.println("Failure dumping service:");
+                e.printStackTrace(pw);
+            }
         }
     }
 
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index 2813e8b..55f9e28 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -33,6 +33,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.proto.ProtoOutputStream;
 
 import libcore.util.ZoneInfoDB;
 
@@ -48,7 +49,7 @@
  * if it is not already running.  Registered alarms are retained while the
  * device is asleep (and can optionally wake the device up if they go off
  * during that time), but will be cleared if it is turned off and rebooted.
- * 
+ *
  * <p>The Alarm Manager holds a CPU wake lock as long as the alarm receiver's
  * onReceive() method is executing. This guarantees that the phone will not sleep
  * until you have finished handling the broadcast. Once onReceive() returns, the
@@ -296,7 +297,7 @@
      * {@link Intent#EXTRA_ALARM_COUNT Intent.EXTRA_ALARM_COUNT} that indicates
      * how many past alarm events have been accumulated into this intent
      * broadcast.  Recurring alarms that have gone undelivered because the
-     * phone was asleep may have a count greater than one when delivered.  
+     * phone was asleep may have a count greater than one when delivered.
      *
      * <div class="note">
      * <p>
@@ -396,10 +397,10 @@
      * set a recurring alarm for the top of every hour but the phone was asleep
      * from 7:45 until 8:45, an alarm will be sent as soon as the phone awakens,
      * then the next alarm will be sent at 9:00.
-     * 
-     * <p>If your application wants to allow the delivery times to drift in 
+     *
+     * <p>If your application wants to allow the delivery times to drift in
      * order to guarantee that at least a certain time interval always elapses
-     * between alarms, then the approach to take is to use one-time alarms, 
+     * between alarms, then the approach to take is to use one-time alarms,
      * scheduling the next one yourself when handling each alarm delivery.
      *
      * <p class="note">
@@ -1056,7 +1057,7 @@
         /**
          * Creates a new alarm clock description.
          *
-         * @param triggerTime time at which the underlying alarm is triggered in wall time 
+         * @param triggerTime time at which the underlying alarm is triggered in wall time
          *                    milliseconds since the epoch
          * @param showIntent an intent that can be used to show or edit details of
          *                        the alarm clock.
@@ -1089,7 +1090,7 @@
          * Returns an intent that can be used to show or edit details of the alarm clock in
          * the application that scheduled it.
          *
-         * <p class="note">Beware that any application can retrieve and send this intent, 
+         * <p class="note">Beware that any application can retrieve and send this intent,
          * potentially with additional fields filled in. See
          * {@link PendingIntent#send(android.content.Context, int, android.content.Intent)
          * PendingIntent.send()} and {@link android.content.Intent#fillIn Intent.fillIn()}
@@ -1121,5 +1122,13 @@
                 return new AlarmClockInfo[size];
             }
         };
+
+        /** @hide */
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+            proto.write(AlarmClockInfoProto.TRIGGER_TIME_MS, mTriggerTime);
+            mShowIntent.writeToProto(proto, AlarmClockInfoProto.SHOW_INTENT);
+            proto.end(token);
+        }
     }
 }
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index a25c226..baeabc3 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -33,6 +33,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.AndroidException;
+import android.util.proto.ProtoOutputStream;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1081,7 +1082,16 @@
         sb.append('}');
         return sb.toString();
     }
-    
+
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        if (mTarget != null) {
+            proto.write(PendingIntentProto.TARGET, mTarget.asBinder().toString());
+        }
+        proto.end(token);
+    }
+
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 1434c9b..1cde73a 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -909,12 +909,21 @@
         }
 
         /**
-         * Set some description of the kind of network type your job needs to have.
-         * Not calling this function means the network is not necessary, as the default is
-         * {@link #NETWORK_TYPE_NONE}.
-         * Bear in mind that calling this function defines network as a strict requirement for your
-         * job. If the network requested is not available your job will never run. See
-         * {@link #setOverrideDeadline(long)} to change this behaviour.
+         * Set some description of the kind of network type your job needs to
+         * have. Not calling this function means the network is not necessary,
+         * as the default is {@link #NETWORK_TYPE_NONE}. Bear in mind that
+         * calling this function defines network as a strict requirement for
+         * your job. If the network requested is not available your job will
+         * never run. See {@link #setOverrideDeadline(long)} to change this
+         * behaviour.
+         * <p class="note">
+         * Note: When your job executes in
+         * {@link JobService#onStartJob(JobParameters)}, be sure to use the
+         * specific network returned by {@link JobParameters#getNetwork()},
+         * otherwise you'll use the default network which may not meet this
+         * constraint.
+         *
+         * @see JobParameters#getNetwork()
          */
         public Builder setRequiredNetworkType(@NetworkType int networkType) {
             mNetworkType = networkType;
diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java
index a6f6be2..5053dc6 100644
--- a/core/java/android/app/job/JobParameters.java
+++ b/core/java/android/app/job/JobParameters.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.app.job.IJobCallback;
 import android.content.ClipData;
+import android.net.Network;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -66,6 +67,7 @@
     private final boolean overrideDeadlineExpired;
     private final Uri[] mTriggeredContentUris;
     private final String[] mTriggeredContentAuthorities;
+    private final Network network;
 
     private int stopReason; // Default value of stopReason is REASON_CANCELED
 
@@ -73,7 +75,7 @@
     public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
             Bundle transientExtras, ClipData clipData, int clipGrantFlags,
             boolean overrideDeadlineExpired, Uri[] triggeredContentUris,
-            String[] triggeredContentAuthorities) {
+            String[] triggeredContentAuthorities, Network network) {
         this.jobId = jobId;
         this.extras = extras;
         this.transientExtras = transientExtras;
@@ -83,6 +85,7 @@
         this.overrideDeadlineExpired = overrideDeadlineExpired;
         this.mTriggeredContentUris = triggeredContentUris;
         this.mTriggeredContentAuthorities = triggeredContentAuthorities;
+        this.network = network;
     }
 
     /**
@@ -171,6 +174,28 @@
     }
 
     /**
+     * Return the network that should be used to perform any network requests
+     * for this job.
+     * <p>
+     * Devices may have multiple active network connections simultaneously, or
+     * they may not have a default network route at all. To correctly handle all
+     * situations like this, your job should always use the network returned by
+     * this method instead of implicitly using the default network route.
+     * <p>
+     * Note that the system may relax the constraints you originally requested,
+     * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over
+     * a metered network when there is a surplus of metered data available.
+     *
+     * @return the network that should be used to perform any network requests
+     *         for this job, or {@code null} if this job didn't set any required
+     *         network type.
+     * @see JobInfo.Builder#setRequiredNetworkType(int)
+     */
+    public @Nullable Network getNetwork() {
+        return network;
+    }
+
+    /**
      * Dequeue the next pending {@link JobWorkItem} from these JobParameters associated with their
      * currently running job.  Calling this method when there is no more work available and all
      * previously dequeued work has been completed will result in the system taking care of
@@ -257,6 +282,11 @@
         overrideDeadlineExpired = in.readInt() == 1;
         mTriggeredContentUris = in.createTypedArray(Uri.CREATOR);
         mTriggeredContentAuthorities = in.createStringArray();
+        if (in.readInt() != 0) {
+            network = Network.CREATOR.createFromParcel(in);
+        } else {
+            network = null;
+        }
         stopReason = in.readInt();
     }
 
@@ -286,6 +316,12 @@
         dest.writeInt(overrideDeadlineExpired ? 1 : 0);
         dest.writeTypedArray(mTriggeredContentUris, flags);
         dest.writeStringArray(mTriggeredContentAuthorities);
+        if (network != null) {
+            dest.writeInt(1);
+            network.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
         dest.writeInt(stopReason);
     }
 
diff --git a/core/java/android/app/usage/AppStandby.java b/core/java/android/app/usage/AppStandby.java
new file mode 100644
index 0000000..6f9fc2f
--- /dev/null
+++ b/core/java/android/app/usage/AppStandby.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.usage;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Set of constants for app standby buckets and reasons. Apps will be moved into different buckets
+ * that affect how frequently they can run in the background or perform other battery-consuming
+ * actions. Buckets will be assigned based on how frequently or when the system thinks the user
+ * is likely to use the app.
+ * @hide
+ */
+public class AppStandby {
+
+    /** The app was used very recently, currently in use or likely to be used very soon. */
+    public static final int STANDBY_BUCKET_ACTIVE = 0;
+
+    // Leave some gap in case we want to increase the number of buckets
+
+    /** The app was used recently and/or likely to be used in the next few hours  */
+    public static final int STANDBY_BUCKET_WORKING_SET = 3;
+
+    // Leave some gap in case we want to increase the number of buckets
+
+    /** The app was used in the last few days and/or likely to be used in the next few days */
+    public static final int STANDBY_BUCKET_FREQUENT = 6;
+
+    // Leave some gap in case we want to increase the number of buckets
+
+    /** The app has not be used for several days and/or is unlikely to be used for several days */
+    public static final int STANDBY_BUCKET_RARE = 9;
+
+    // Leave some gap in case we want to increase the number of buckets
+
+    /** The app has never been used. */
+    public static final int STANDBY_BUCKET_NEVER = 12;
+
+    /** Reason for bucketing -- default initial state */
+    public static final String REASON_DEFAULT = "default";
+
+    /** Reason for bucketing -- timeout */
+    public static final String REASON_TIMEOUT = "timeout";
+
+    /** Reason for bucketing -- usage */
+    public static final String REASON_USAGE = "usage";
+
+    /** Reason for bucketing -- forced by user / shell command */
+    public static final String REASON_FORCED = "forced";
+
+    /**
+     * Reason for bucketing -- predicted. This is a prefix and the UID of the bucketeer will
+     * be appended.
+     */
+    public static final String REASON_PREDICTED = "predicted";
+
+    @IntDef(flag = false, value = {
+            STANDBY_BUCKET_ACTIVE,
+            STANDBY_BUCKET_WORKING_SET,
+            STANDBY_BUCKET_FREQUENT,
+            STANDBY_BUCKET_RARE,
+            STANDBY_BUCKET_NEVER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StandbyBuckets {}
+}
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 31b2359..4fbbdf2 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -36,4 +36,6 @@
     void onCarrierPrivilegedAppsChanged();
     void reportChooserSelection(String packageName, int userId, String contentType,
             in String[] annotations, String action);
+    int getAppStandbyBucket(String packageName, String callingPackage, int userId);
+    void setAppStandbyBucket(String packageName, int bucket, int userId);
 }
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index fd579fc..c827432 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -19,6 +19,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.app.usage.AppStandby.StandbyBuckets;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.os.RemoteException;
@@ -247,6 +248,29 @@
     }
 
     /**
+     * @hide
+     */
+    public @StandbyBuckets int getAppStandbyBucket(String packageName) {
+        try {
+            return mService.getAppStandbyBucket(packageName, mContext.getOpPackageName(),
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+        }
+        return AppStandby.STANDBY_BUCKET_ACTIVE;
+    }
+
+    /**
+     * @hide
+     */
+    public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) {
+        try {
+            mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId());
+        } catch (RemoteException e) {
+            // Nothing to do
+        }
+    }
+
+    /**
      * {@hide}
      * Temporarily whitelist the specified app for a short duration. This is to allow an app
      * receiving a high priority message to be able to access the network and acquire wakelocks
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index dc9970a..ab0eb92 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -19,8 +19,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.graphics.Color;
@@ -66,11 +64,8 @@
 
     // When we're inflating the initialLayout for a AppWidget, we only allow
     // views that are allowed in RemoteViews.
-    static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() {
-        public boolean onLoadClass(Class clazz) {
-            return clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
-        }
-    };
+    private static final LayoutInflater.Filter INFLATER_FILTER =
+            (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
 
     Context mContext;
     Context mRemoteContext;
@@ -136,13 +131,19 @@
         mAppWidgetId = appWidgetId;
         mInfo = info;
 
+        // We add padding to the AppWidgetHostView if necessary
+        Rect padding = getDefaultPadding();
+        setPadding(padding.left, padding.top, padding.right, padding.bottom);
+
         // Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for
         // a widget, eg. for some widgets in safe mode.
         if (info != null) {
-            // We add padding to the AppWidgetHostView if necessary
-            Rect padding = getDefaultPaddingForWidget(mContext, info.provider, null);
-            setPadding(padding.left, padding.top, padding.right, padding.bottom);
-            updateContentDescription(info);
+            String description = info.loadLabel(getContext().getPackageManager());
+            if ((info.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
+                description = Resources.getSystem().getString(
+                        com.android.internal.R.string.suspended_widget_accessibility, description);
+            }
+            setContentDescription(description);
         }
     }
 
@@ -164,23 +165,23 @@
      */
     public static Rect getDefaultPaddingForWidget(Context context, ComponentName component,
             Rect padding) {
-        PackageManager packageManager = context.getPackageManager();
-        ApplicationInfo appInfo;
+        ApplicationInfo appInfo = null;
+        try {
+            appInfo = context.getPackageManager().getApplicationInfo(component.getPackageName(), 0);
+        } catch (NameNotFoundException e) {
+            // if we can't find the package, ignore
+        }
+        return getDefaultPaddingForWidget(context, appInfo, padding);
+    }
 
+    private static Rect getDefaultPaddingForWidget(Context context, ApplicationInfo appInfo,
+            Rect padding) {
         if (padding == null) {
             padding = new Rect(0, 0, 0, 0);
         } else {
             padding.set(0, 0, 0, 0);
         }
-
-        try {
-            appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0);
-        } catch (NameNotFoundException e) {
-            // if we can't find the package, return 0 padding
-            return padding;
-        }
-
-        if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+        if (appInfo != null && appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
             Resources r = context.getResources();
             padding.left = r.getDimensionPixelSize(com.android.internal.
                     R.dimen.default_app_widget_padding_left);
@@ -194,6 +195,11 @@
         return padding;
     }
 
+    private Rect getDefaultPadding() {
+        return getDefaultPaddingForWidget(mContext,
+                mInfo == null ? null : mInfo.providerInfo.applicationInfo, null);
+    }
+
     public int getAppWidgetId() {
         return mAppWidgetId;
     }
@@ -284,10 +290,7 @@
             newOptions = new Bundle();
         }
 
-        Rect padding = new Rect();
-        if (mInfo != null) {
-            padding = getDefaultPaddingForWidget(mContext, mInfo.provider, padding);
-        }
+        Rect padding = getDefaultPadding();
         float density = getResources().getDisplayMetrics().density;
 
         int xPaddingDips = (int) ((padding.left + padding.right) / density);
@@ -361,7 +364,7 @@
      * initial layout.
      */
     void resetAppWidget(AppWidgetProviderInfo info) {
-        mInfo = info;
+        setAppWidget(mAppWidgetId, info);
         mViewMode = VIEW_MODE_NOINIT;
         updateAppWidget(null);
     }
@@ -433,7 +436,6 @@
         }
 
         applyContent(content, recycled, exception);
-        updateContentDescription(mInfo);
     }
 
     private void applyContent(View content, boolean recycled, Exception exception) {
@@ -460,27 +462,6 @@
         }
     }
 
-    private void updateContentDescription(AppWidgetProviderInfo info) {
-        if (info != null) {
-            LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class);
-            ApplicationInfo appInfo = null;
-            try {
-                appInfo = launcherApps.getApplicationInfo(
-                        info.provider.getPackageName(), 0, info.getProfile());
-            } catch (NameNotFoundException e) {
-                // ignore -- use null.
-            }
-            if (appInfo != null &&
-                    (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
-                setContentDescription(
-                        Resources.getSystem().getString(
-                        com.android.internal.R.string.suspended_widget_accessibility, info.label));
-            } else {
-                setContentDescription(info.label);
-            }
-        }
-    }
-
     private void inflateAsync(RemoteViews remoteViews) {
         // Prepare a local reference to the remote Context so we're ready to
         // inflate any requested LayoutParams.
@@ -614,7 +595,7 @@
                 LayoutInflater inflater = (LayoutInflater)
                         theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                 inflater = inflater.cloneInContext(theirContext);
-                inflater.setFilter(sInflaterFilter);
+                inflater.setFilter(INFLATER_FILTER);
                 AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
                 Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
 
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index dcc6821..2034280 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1463,47 +1463,6 @@
         }
     }
 
-    /**
-     * @hide
-     */
-    public boolean isForwardLocked() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0;
-    }
-
-    /**
-     * @hide
-     */
-    @TestApi
-    public boolean isSystemApp() {
-        return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-    }
-
-    /**
-     * @hide
-     */
-    @TestApi
-    public boolean isPrivilegedApp() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
-    }
-
-    /**
-     * @hide
-     */
-    public boolean isUpdatedSystemApp() {
-        return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
-    }
-
-    /** @hide */
-    public boolean isInternal() {
-        return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0;
-    }
-
-    /** @hide */
-    public boolean isExternalAsec() {
-        return TextUtils.isEmpty(volumeUuid)
-                && (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
-    }
-
     /** @hide */
     public boolean isDefaultToDeviceProtectedStorage() {
         return (privateFlags
@@ -1516,45 +1475,72 @@
     }
 
     /** @hide */
+    public boolean isEncryptionAware() {
+        return isDirectBootAware() || isPartiallyDirectBootAware();
+    }
+
+    /** @hide */
+    public boolean isExternal() {
+        return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
+    }
+
+    /** @hide */
+    public boolean isExternalAsec() {
+        return TextUtils.isEmpty(volumeUuid) && isExternal();
+    }
+
+    /** @hide */
+    public boolean isForwardLocked() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0;
+    }
+
+    /** @hide */
+    public boolean isInstantApp() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
+    }
+
+    /** @hide */
+    public boolean isInternal() {
+        return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0;
+    }
+
+    /** @hide */
+    public boolean isOem() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
+    }
+
+    /** @hide */
     public boolean isPartiallyDirectBootAware() {
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0;
     }
 
     /** @hide */
-    public boolean isEncryptionAware() {
-        return isDirectBootAware() || isPartiallyDirectBootAware();
+    @TestApi
+    public boolean isPrivilegedApp() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
     }
 
-    /**
-     * @hide
-     */
-    public boolean isInstantApp() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
-    }
-
-    /**
-     * @hide
-     */
+    /** @hide */
     public boolean isRequiredForSystemUser() {
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0;
     }
 
-    /**
-     * Returns true if the app has declared in its manifest that it wants its split APKs to be
-     * loaded into isolated Contexts, with their own ClassLoaders and Resources objects.
-     * @hide
-     */
-    public boolean requestsIsolatedSplitLoading() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0;
-    }
-
-    /**
-     * @hide
-     */
+    /** @hide */
     public boolean isStaticSharedLibrary() {
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY) != 0;
     }
 
+    /** @hide */
+    @TestApi
+    public boolean isSystemApp() {
+        return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+    /** @hide */
+    public boolean isUpdatedSystemApp() {
+        return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+    }
+
     /**
      * Returns whether or not this application was installed as a virtual preload.
      */
@@ -1563,10 +1549,12 @@
     }
 
     /**
+     * Returns true if the app has declared in its manifest that it wants its split APKs to be
+     * loaded into isolated Contexts, with their own ClassLoaders and Resources objects.
      * @hide
      */
-    public boolean isOem() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
+    public boolean requestsIsolatedSplitLoading() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0;
     }
 
     /**
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 7d58658..14cf855 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -337,6 +337,12 @@
     public abstract boolean isPackagePersistent(String packageName);
 
     /**
+     * Returns whether or not the given package represents a legacy system application released
+     * prior to runtime permissions.
+     */
+    public abstract boolean isLegacySystemApp(PackageParser.Package pkg);
+
+    /**
      * Get all overlay packages for a user.
      * @param userId The user for which to get the overlays.
      * @return A list of overlay packages. An empty list is returned if the
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index ad36139a..b48829c 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6221,48 +6221,48 @@
             return false;
         }
 
-        /**
-         * @hide
-         */
+        /** @hide */
+        public boolean isExternal() {
+            return applicationInfo.isExternal();
+        }
+
+        /** @hide */
         public boolean isForwardLocked() {
             return applicationInfo.isForwardLocked();
         }
 
-        /**
-         * @hide
-         */
-        public boolean isSystemApp() {
-            return applicationInfo.isSystemApp();
+        /** @hide */
+        public boolean isOem() {
+            return applicationInfo.isOem();
         }
 
-        /**
-         * @hide
-         */
-        public boolean isPrivilegedApp() {
+        /** @hide */
+        public boolean isPrivileged() {
             return applicationInfo.isPrivilegedApp();
         }
 
-        /**
-         * @hide
-         */
+        /** @hide */
+        public boolean isSystem() {
+            return applicationInfo.isSystemApp();
+        }
+
+        /** @hide */
         public boolean isUpdatedSystemApp() {
             return applicationInfo.isUpdatedSystemApp();
         }
 
-        /**
-         * @hide
-         */
+        /** @hide */
         public boolean canHaveOatDir() {
             // The following app types CANNOT have oat directory
             // - non-updated system apps
             // - forward-locked apps or apps installed in ASEC containers
-            return (!isSystemApp() || isUpdatedSystemApp())
+            return (!isSystem() || isUpdatedSystemApp())
                     && !isForwardLocked() && !applicationInfo.isExternalAsec();
         }
 
         public boolean isMatch(int flags) {
             if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
-                return isSystemApp();
+                return isSystem();
             }
             return true;
         }
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 595d857..996824d 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -102,7 +102,7 @@
             "android.hardware.usb.action.USB_PORT_CHANGED";
 
    /**
-     * Broadcast Action:  A broadcast for USB device attached event.
+     * Activity intent sent when a USB device is attached.
      *
      * This intent is sent when a USB device is attached to the USB bus when in host mode.
      * <ul>
@@ -128,9 +128,8 @@
             "android.hardware.usb.action.USB_DEVICE_DETACHED";
 
    /**
-     * Broadcast Action:  A broadcast for USB accessory attached event.
+     * Activity intent sent when a USB accessory is attached.
      *
-     * This intent is sent when a USB accessory is attached.
      * <ul>
      * <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.usb.UsbAccessory}
      * for the attached accessory
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 4bb8844..db12dd9 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.IntDef;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -23,6 +24,8 @@
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 import java.util.StringJoiner;
 
@@ -77,6 +80,31 @@
      */
     private long mNetworkCapabilities;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "NET_CAPABILITY_" }, value = {
+            NET_CAPABILITY_MMS,
+            NET_CAPABILITY_SUPL,
+            NET_CAPABILITY_DUN,
+            NET_CAPABILITY_FOTA,
+            NET_CAPABILITY_IMS,
+            NET_CAPABILITY_CBS,
+            NET_CAPABILITY_WIFI_P2P,
+            NET_CAPABILITY_IA,
+            NET_CAPABILITY_RCS,
+            NET_CAPABILITY_XCAP,
+            NET_CAPABILITY_EIMS,
+            NET_CAPABILITY_NOT_METERED,
+            NET_CAPABILITY_INTERNET,
+            NET_CAPABILITY_NOT_RESTRICTED,
+            NET_CAPABILITY_TRUSTED,
+            NET_CAPABILITY_NOT_VPN,
+            NET_CAPABILITY_VALIDATED,
+            NET_CAPABILITY_CAPTIVE_PORTAL,
+            NET_CAPABILITY_FOREGROUND,
+    })
+    public @interface NetCapability { }
+
     /**
      * Indicates this is a network that has the ability to reach the
      * carrier's MMSC for sending and receiving MMS messages.
@@ -260,11 +288,11 @@
      * Multiple capabilities may be applied sequentially.  Note that when searching
      * for a network to satisfy a request, all capabilities requested must be satisfied.
      *
-     * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be added.
+     * @param capability the capability to be added.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public NetworkCapabilities addCapability(int capability) {
+    public NetworkCapabilities addCapability(@NetCapability int capability) {
         if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
             throw new IllegalArgumentException("NetworkCapability out of range");
         }
@@ -275,11 +303,11 @@
     /**
      * Removes (if found) the given capability from this {@code NetworkCapability} instance.
      *
-     * @param capability the {@code NetworkCapabilities.NET_CAPABILTIY_*} to be removed.
+     * @param capability the capability to be removed.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public NetworkCapabilities removeCapability(int capability) {
+    public NetworkCapabilities removeCapability(@NetCapability int capability) {
         if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
             throw new IllegalArgumentException("NetworkCapability out of range");
         }
@@ -290,21 +318,20 @@
     /**
      * Gets all the capabilities set on this {@code NetworkCapability} instance.
      *
-     * @return an array of {@code NetworkCapabilities.NET_CAPABILITY_*} values
-     *         for this instance.
+     * @return an array of capability values for this instance.
      * @hide
      */
-    public int[] getCapabilities() {
+    public @NetCapability int[] getCapabilities() {
         return BitUtils.unpackBits(mNetworkCapabilities);
     }
 
     /**
      * Tests for the presence of a capabilitity on this instance.
      *
-     * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be tested for.
+     * @param capability the capabilities to be tested for.
      * @return {@code true} if set on this instance.
      */
-    public boolean hasCapability(int capability) {
+    public boolean hasCapability(@NetCapability int capability) {
         if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
             return false;
         }
@@ -385,6 +412,19 @@
      */
     private long mTransportTypes;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "TRANSPORT_" }, value = {
+            TRANSPORT_CELLULAR,
+            TRANSPORT_WIFI,
+            TRANSPORT_BLUETOOTH,
+            TRANSPORT_ETHERNET,
+            TRANSPORT_VPN,
+            TRANSPORT_WIFI_AWARE,
+            TRANSPORT_LOWPAN,
+    })
+    public @interface Transport { }
+
     /**
      * Indicates this network uses a Cellular transport.
      */
@@ -426,7 +466,7 @@
     public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN;
 
     /** @hide */
-    public static boolean isValidTransport(int transportType) {
+    public static boolean isValidTransport(@Transport int transportType) {
         return (MIN_TRANSPORT <= transportType) && (transportType <= MAX_TRANSPORT);
     }
 
@@ -449,11 +489,11 @@
      * to be selected.  This is logically different than
      * {@code NetworkCapabilities.NET_CAPABILITY_*} listed above.
      *
-     * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be added.
+     * @param transportType the transport type to be added.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public NetworkCapabilities addTransportType(int transportType) {
+    public NetworkCapabilities addTransportType(@Transport int transportType) {
         checkValidTransportType(transportType);
         mTransportTypes |= 1 << transportType;
         setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
@@ -463,11 +503,11 @@
     /**
      * Removes (if found) the given transport from this {@code NetworkCapability} instance.
      *
-     * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be removed.
+     * @param transportType the transport type to be removed.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public NetworkCapabilities removeTransportType(int transportType) {
+    public NetworkCapabilities removeTransportType(@Transport int transportType) {
         checkValidTransportType(transportType);
         mTransportTypes &= ~(1 << transportType);
         setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
@@ -477,21 +517,20 @@
     /**
      * Gets all the transports set on this {@code NetworkCapability} instance.
      *
-     * @return an array of {@code NetworkCapabilities.TRANSPORT_*} values
-     *         for this instance.
+     * @return an array of transport type values for this instance.
      * @hide
      */
-    public int[] getTransportTypes() {
+    public @Transport int[] getTransportTypes() {
         return BitUtils.unpackBits(mTransportTypes);
     }
 
     /**
      * Tests for the presence of a transport on this instance.
      *
-     * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be tested for.
+     * @param transportType the transport type to be tested for.
      * @return {@code true} if set on this instance.
      */
-    public boolean hasTransport(int transportType) {
+    public boolean hasTransport(@Transport int transportType) {
         return isValidTransport(transportType) && ((mTransportTypes & (1 << transportType)) != 0);
     }
 
@@ -896,7 +935,7 @@
     /**
      * @hide
      */
-    public static String capabilityNamesOf(int[] capabilities) {
+    public static String capabilityNamesOf(@NetCapability int[] capabilities) {
         StringJoiner joiner = new StringJoiner("|");
         if (capabilities != null) {
             for (int c : capabilities) {
@@ -909,7 +948,7 @@
     /**
      * @hide
      */
-    public static String capabilityNameOf(int capability) {
+    public static String capabilityNameOf(@NetCapability int capability) {
         switch (capability) {
             case NET_CAPABILITY_MMS:            return "MMS";
             case NET_CAPABILITY_SUPL:           return "SUPL";
@@ -937,7 +976,7 @@
     /**
      * @hide
      */
-    public static String transportNamesOf(int[] types) {
+    public static String transportNamesOf(@Transport int[] types) {
         StringJoiner joiner = new StringJoiner("|");
         if (types != null) {
             for (int t : types) {
@@ -950,14 +989,14 @@
     /**
      * @hide
      */
-    public static String transportNameOf(int transport) {
+    public static String transportNameOf(@Transport int transport) {
         if (!isValidTransport(transport)) {
             return "UNKNOWN";
         }
         return TRANSPORT_NAMES[transport];
     }
 
-    private static void checkValidTransportType(int transport) {
+    private static void checkValidTransportType(@Transport int transport) {
         Preconditions.checkArgument(
                 isValidTransport(transport), "Invalid TransportType " + transport);
     }
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 95a8bb4..25b1705 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -155,14 +155,13 @@
          * Add the given capability requirement to this builder.  These represent
          * the requested network's required capabilities.  Note that when searching
          * for a network to satisfy a request, all capabilities requested must be
-         * satisfied.  See {@link NetworkCapabilities} for {@code NET_CAPABILITY_*}
-         * definitions.
+         * satisfied.
          *
-         * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to add.
+         * @param capability The capability to add.
          * @return The builder to facilitate chaining
          *         {@code builder.addCapability(...).addCapability();}.
          */
-        public Builder addCapability(int capability) {
+        public Builder addCapability(@NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.addCapability(capability);
             return this;
         }
@@ -170,10 +169,10 @@
         /**
          * Removes (if found) the given capability from this builder instance.
          *
-         * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to remove.
+         * @param capability The capability to remove.
          * @return The builder to facilitate chaining.
          */
-        public Builder removeCapability(int capability) {
+        public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.removeCapability(capability);
             return this;
         }
@@ -208,13 +207,12 @@
          * Adds the given transport requirement to this builder.  These represent
          * the set of allowed transports for the request.  Only networks using one
          * of these transports will satisfy the request.  If no particular transports
-         * are required, none should be specified here.  See {@link NetworkCapabilities}
-         * for {@code TRANSPORT_*} definitions.
+         * are required, none should be specified here.
          *
-         * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to add.
+         * @param transportType The transport type to add.
          * @return The builder to facilitate chaining.
          */
-        public Builder addTransportType(int transportType) {
+        public Builder addTransportType(@NetworkCapabilities.Transport int transportType) {
             mNetworkCapabilities.addTransportType(transportType);
             return this;
         }
@@ -222,10 +220,10 @@
         /**
          * Removes (if found) the given transport from this builder instance.
          *
-         * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to remove.
+         * @param transportType The transport type to remove.
          * @return The builder to facilitate chaining.
          */
-        public Builder removeTransportType(int transportType) {
+        public Builder removeTransportType(@NetworkCapabilities.Transport int transportType) {
             mNetworkCapabilities.removeTransportType(transportType);
             return this;
         }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 8682c01..a8bd940 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1986,7 +1986,7 @@
     public abstract long getDeviceIdlingTime(int mode, long elapsedRealtimeUs, int which);
 
     /**
-     * Returns the number of times that the devie has started idling.
+     * Returns the number of times that the device has started idling.
      *
      * {@hide}
      */
@@ -6453,7 +6453,7 @@
                 pw.println();
             }
         }
-        if (!filtering || (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0) {
+        if (!filtering || (flags & DUMP_DAILY_ONLY) != 0) {
             pw.println("Daily stats:");
             pw.print("  Current start time: ");
             pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index e9e695b..66e1651 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -167,7 +167,7 @@
         try {
             if (binder instanceof BinderProxy) {
                 ((BinderProxy) binder).mWarnOnBlocking = false;
-            } else if (binder != null
+            } else if (binder != null && binder.getInterfaceDescriptor() != null
                     && binder.queryLocalInterface(binder.getInterfaceDescriptor()) == null) {
                 Log.w(TAG, "Unable to allow blocking on interface " + binder);
             }
@@ -414,7 +414,7 @@
      * descriptor.
      */
     public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
-        if (mDescriptor.equals(descriptor)) {
+        if (mDescriptor != null && mDescriptor.equals(descriptor)) {
             return mOwner;
         }
         return null;
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index e4a12e8..6223235 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.util.Slog;
+
 import com.android.internal.util.FastPrintWriter;
 
 import java.io.BufferedInputStream;
@@ -118,13 +119,33 @@
                 mErrPrintWriter.flush();
             }
             if (DEBUG) Slog.d(TAG, "Sending command result on " + mTarget);
-            mResultReceiver.send(res, null);
+            if (mResultReceiver != null) {
+                mResultReceiver.send(res, null);
+            }
         }
         if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget);
         return res;
     }
 
     /**
+     * Adopt the ResultReceiver that was given to this shell command from it, taking
+     * it over.  Primarily used to dispatch to another shell command.  Once called,
+     * this shell command will no longer return its own result when done.
+     */
+    public ResultReceiver adoptResultReceiver() {
+        ResultReceiver rr = mResultReceiver;
+        mResultReceiver = null;
+        return rr;
+    }
+
+    /**
+     * Return the raw FileDescriptor for the output stream.
+     */
+    public FileDescriptor getOutFileDescriptor() {
+        return mOut;
+    }
+
+    /**
      * Return direct raw access (not buffered) to the command's output data stream.
      */
     public OutputStream getRawOutputStream() {
@@ -145,6 +166,13 @@
     }
 
     /**
+     * Return the raw FileDescriptor for the error stream.
+     */
+    public FileDescriptor getErrFileDescriptor() {
+        return mErr;
+    }
+
+    /**
      * Return direct raw access (not buffered) to the command's error output data stream.
      */
     public OutputStream getRawErrorStream() {
@@ -168,6 +196,13 @@
     }
 
     /**
+     * Return the raw FileDescriptor for the input stream.
+     */
+    public FileDescriptor getInFileDescriptor() {
+        return mIn;
+    }
+
+    /**
      * Return direct raw access (not buffered) to the command's input data stream.
      */
     public InputStream getRawInputStream() {
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index ac5c2e9..4d2a962 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1910,7 +1910,7 @@
         MeasuredText mt = MeasuredText.obtain();
         TextLine tl = TextLine.obtain();
         try {
-            mt.setPara(text, start, end, textDir, null);
+            mt.setPara(text, start, end, textDir);
             Directions directions;
             int dir;
             if (mt.mEasy) {
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index ffc44a7..3d9fba7 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -39,7 +39,6 @@
 
     private int mPos;
     private TextPaint mWorkPaint;
-    private StaticLayout.Builder mBuilder;
 
     private MeasuredText() {
         mWorkPaint = new TextPaint();
@@ -82,7 +81,6 @@
 
     void finish() {
         mText = null;
-        mBuilder = null;
         if (mLen > 1000) {
             mWidths = null;
             mChars = null;
@@ -93,9 +91,7 @@
     /**
      * Analyzes text for bidirectional runs.  Allocates working buffers.
      */
-    void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir,
-            StaticLayout.Builder builder) {
-        mBuilder = builder;
+    void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
         mText = text;
         mTextStart = start;
 
@@ -159,12 +155,12 @@
     /**
      * Apply the style.
      *
-     * If StaticLyaout.Builder is not provided in setPara() method, this method measures the styled
-     * text width.
-     * If StaticLayout.Builder is provided in setPara() method, this method just passes the style
-     * information to native code by calling StaticLayout.Builder.addstyleRun() and returns 0.
+     * If nativeStaticLayoutPtr is 0, this method measures the styled text width.
+     * If nativeStaticLayoutPtr is not 0, this method just passes the style information to native
+     * code by calling StaticLayout.addstyleRun() and returns 0.
      */
-    float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+    float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm,
+            long nativeStaticLayoutPtr) {
         if (fm != null) {
             paint.getFontMetricsInt(fm);
         }
@@ -174,10 +170,10 @@
 
         if (mEasy) {
             final boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
-            if (mBuilder == null) {
+            if (nativeStaticLayoutPtr == 0) {
                 return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p);
             } else {
-                mBuilder.addStyleRun(paint, p, p + len, isRtl);
+                StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, p, p + len, isRtl);
                 return 0.0f;  // Builder.addStyleRun doesn't return the width.
             }
         }
@@ -187,12 +183,12 @@
         for (int q = p, i = p + 1, e = p + len;; ++i) {
             if (i == e || mLevels[i] != level) {
                 final boolean isRtl = (level & 0x1) != 0;
-                if (mBuilder == null) {
+                if (nativeStaticLayoutPtr == 0) {
                     totalAdvance +=
                             paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q);
                 } else {
                     // Builder.addStyleRun doesn't return the width.
-                    mBuilder.addStyleRun(paint, q, i, isRtl);
+                    StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, q, i, isRtl);
                 }
                 if (i == e) {
                     break;
@@ -201,11 +197,15 @@
                 level = mLevels[i];
             }
         }
-        return totalAdvance;  // If mBuilder is null, the result is zero.
+        return totalAdvance;  // If nativeStaticLayoutPtr is 0, the result is zero.
+    }
+
+    float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+        return addStyleRun(paint, len, fm, 0 /* native ptr */);
     }
 
     float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
-            Paint.FontMetricsInt fm) {
+            Paint.FontMetricsInt fm, long nativeStaticLayoutPtr) {
 
         TextPaint workPaint = mWorkPaint;
         workPaint.set(paint);
@@ -224,18 +224,18 @@
 
         float wid;
         if (replacement == null) {
-            wid = addStyleRun(workPaint, len, fm);
+            wid = addStyleRun(workPaint, len, fm, nativeStaticLayoutPtr);
         } else {
             // Use original text.  Shouldn't matter.
             wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
                     mTextStart + mPos + len, fm);
-            if (mBuilder == null) {
+            if (nativeStaticLayoutPtr == 0) {
                 float[] w = mWidths;
                 w[mPos] = wid;
                 for (int i = mPos + 1, e = mPos + len; i < e; i++)
                     w[i] = 0;
             } else {
-                mBuilder.addReplacementRun(paint, mPos, mPos + len, wid);
+                StaticLayout.addReplacementRun(nativeStaticLayoutPtr, paint, mPos, mPos + len, wid);
             }
             mPos += len;
         }
@@ -253,6 +253,11 @@
         return wid;
     }
 
+    float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
+            Paint.FontMetricsInt fm) {
+        return addStyleRun(paint, spans, len, fm, 0 /* native ptr */);
+    }
+
     int breakText(int limit, boolean forwards, float width) {
         float[] w = mWidths;
         if (forwards) {
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 5c60188..c0fc44f 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -32,6 +32,9 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
 
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
 import java.util.Arrays;
 
 /**
@@ -57,9 +60,7 @@
      * default values.
      */
     public final static class Builder {
-        private Builder() {
-            mNativePtr = nNewBuilder();
-        }
+        private Builder() {}
 
         /**
          * Obtain a builder for constructing StaticLayout objects.
@@ -116,13 +117,11 @@
             b.mRightIndents = null;
             b.mLeftPaddings = null;
             b.mRightPaddings = null;
-            nFinishBuilder(b.mNativePtr);
             sPool.release(b);
         }
 
         // release any expensive state
         /* package */ void finish() {
-            nFinishBuilder(mNativePtr);
             mText = null;
             mPaint = null;
             mLeftIndents = null;
@@ -405,32 +404,6 @@
         }
 
         /**
-         * Measurement and break iteration is done in native code. The protocol for using
-         * the native code is as follows.
-         *
-         * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
-         * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
-         * future).
-         *
-         * Then, for each run within the paragraph:
-         *  - one of the following, depending on the type of run:
-         *    + addStyleRun (a text run, to be measured in native code)
-         *    + addReplacementRun (a replacement run, width is given)
-         *
-         * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
-         *
-         * After all paragraphs, call finish() to release expensive buffers.
-         */
-
-        /* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
-            nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
-        }
-
-        /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
-            nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width);
-        }
-
-        /**
          * Build the {@link StaticLayout} after options have been set.
          *
          * <p>Note: the builder object must not be reused in any way after calling this
@@ -446,17 +419,6 @@
             return result;
         }
 
-        @Override
-        protected void finalize() throws Throwable {
-            try {
-                nFreeBuilder(mNativePtr);
-            } finally {
-                super.finalize();
-            }
-        }
-
-        /* package */ long mNativePtr;
-
         private CharSequence mText;
         private int mStart;
         private int mEnd;
@@ -694,270 +656,294 @@
             indents = null;
         }
 
-        int paraEnd;
-        for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
-            paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
-            if (paraEnd < 0)
-                paraEnd = bufEnd;
-            else
-                paraEnd++;
+        final long nativePtr = nInit(
+                b.mBreakStrategy, b.mHyphenationFrequency,
+                // TODO: Support more justification mode, e.g. letter spacing, stretching.
+                b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
+                indents, mLeftPaddings, mRightPaddings);
 
-            int firstWidthLineCount = 1;
-            int firstWidth = outerWidth;
-            int restWidth = outerWidth;
+        try {
+            int paraEnd;
+            for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
+                paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
+                if (paraEnd < 0) {
+                    paraEnd = bufEnd;
+                } else {
+                    paraEnd++;
+                }
 
-            LineHeightSpan[] chooseHt = null;
+                int firstWidthLineCount = 1;
+                int firstWidth = outerWidth;
+                int restWidth = outerWidth;
 
-            if (spanned != null) {
-                LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
-                        LeadingMarginSpan.class);
-                for (int i = 0; i < sp.length; i++) {
-                    LeadingMarginSpan lms = sp[i];
-                    firstWidth -= sp[i].getLeadingMargin(true);
-                    restWidth -= sp[i].getLeadingMargin(false);
+                LineHeightSpan[] chooseHt = null;
 
-                    // LeadingMarginSpan2 is odd.  The count affects all
-                    // leading margin spans, not just this particular one
-                    if (lms instanceof LeadingMarginSpan2) {
-                        LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
-                        firstWidthLineCount = Math.max(firstWidthLineCount,
-                                lms2.getLeadingMarginLineCount());
+                if (spanned != null) {
+                    LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
+                            LeadingMarginSpan.class);
+                    for (int i = 0; i < sp.length; i++) {
+                        LeadingMarginSpan lms = sp[i];
+                        firstWidth -= sp[i].getLeadingMargin(true);
+                        restWidth -= sp[i].getLeadingMargin(false);
+
+                        // LeadingMarginSpan2 is odd.  The count affects all
+                        // leading margin spans, not just this particular one
+                        if (lms instanceof LeadingMarginSpan2) {
+                            LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+                            firstWidthLineCount = Math.max(firstWidthLineCount,
+                                    lms2.getLeadingMarginLineCount());
+                        }
+                    }
+
+                    chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+
+                    if (chooseHt.length == 0) {
+                        chooseHt = null; // So that out() would not assume it has any contents
+                    } else {
+                        if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
+                            chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+                        }
+
+                        for (int i = 0; i < chooseHt.length; i++) {
+                            int o = spanned.getSpanStart(chooseHt[i]);
+
+                            if (o < paraStart) {
+                                // starts in this layout, before the
+                                // current paragraph
+
+                                chooseHtv[i] = getLineTop(getLineForOffset(o));
+                            } else {
+                                // starts in this paragraph
+
+                                chooseHtv[i] = v;
+                            }
+                        }
                     }
                 }
 
-                chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+                measured.setPara(source, paraStart, paraEnd, textDir);
+                char[] chs = measured.mChars;
+                float[] widths = measured.mWidths;
+                byte[] chdirs = measured.mLevels;
+                int dir = measured.mDir;
+                boolean easy = measured.mEasy;
 
-                if (chooseHt.length == 0) {
-                    chooseHt = null; // So that out() would not assume it has any contents
-                } else {
-                    if (chooseHtv == null ||
-                        chooseHtv.length < chooseHt.length) {
-                        chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+                // tab stop locations
+                int[] variableTabStops = null;
+                if (spanned != null) {
+                    TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+                            paraEnd, TabStopSpan.class);
+                    if (spans.length > 0) {
+                        int[] stops = new int[spans.length];
+                        for (int i = 0; i < spans.length; i++) {
+                            stops[i] = spans[i].getTabStop();
+                        }
+                        Arrays.sort(stops, 0, stops.length);
+                        variableTabStops = stops;
+                    }
+                }
+
+                // measurement has to be done before performing line breaking
+                // but we don't want to recompute fontmetrics or span ranges the
+                // second time, so we cache those and then use those stored values
+                int fmCacheCount = 0;
+                int spanEndCacheCount = 0;
+                for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+                    if (fmCacheCount * 4 >= fmCache.length) {
+                        int[] grow = new int[fmCacheCount * 4 * 2];
+                        System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
+                        fmCache = grow;
                     }
 
-                    for (int i = 0; i < chooseHt.length; i++) {
-                        int o = spanned.getSpanStart(chooseHt[i]);
+                    if (spanEndCacheCount >= spanEndCache.length) {
+                        int[] grow = new int[spanEndCacheCount * 2];
+                        System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
+                        spanEndCache = grow;
+                    }
 
-                        if (o < paraStart) {
-                            // starts in this layout, before the
-                            // current paragraph
+                    if (spanned == null) {
+                        spanEnd = paraEnd;
+                        int spanLen = spanEnd - spanStart;
+                        measured.addStyleRun(paint, spanLen, fm, nativePtr);
+                    } else {
+                        spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
+                                MetricAffectingSpan.class);
+                        int spanLen = spanEnd - spanStart;
+                        MetricAffectingSpan[] spans =
+                                spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
+                        spans = TextUtils.removeEmptySpans(spans, spanned,
+                                MetricAffectingSpan.class);
+                        measured.addStyleRun(paint, spans, spanLen, fm, nativePtr);
+                    }
 
-                            chooseHtv[i] = getLineTop(getLineForOffset(o));
+                    // the order of storage here (top, bottom, ascent, descent) has to match the
+                    // code below where these values are retrieved
+                    fmCache[fmCacheCount * 4 + 0] = fm.top;
+                    fmCache[fmCacheCount * 4 + 1] = fm.bottom;
+                    fmCache[fmCacheCount * 4 + 2] = fm.ascent;
+                    fmCache[fmCacheCount * 4 + 3] = fm.descent;
+                    fmCacheCount++;
+
+                    spanEndCache[spanEndCacheCount] = spanEnd;
+                    spanEndCacheCount++;
+                }
+
+                int breakCount = nComputeLineBreaks(
+                        nativePtr,
+
+                        // Inputs
+                        chs,
+                        paraEnd - paraStart,
+                        firstWidth,
+                        firstWidthLineCount,
+                        restWidth,
+                        variableTabStops,
+                        TAB_INCREMENT,
+                        mLineCount,
+
+                        // Outputs
+                        lineBreaks,
+                        lineBreaks.breaks.length,
+                        lineBreaks.breaks,
+                        lineBreaks.widths,
+                        lineBreaks.ascents,
+                        lineBreaks.descents,
+                        lineBreaks.flags,
+                        widths);
+
+                final int[] breaks = lineBreaks.breaks;
+                final float[] lineWidths = lineBreaks.widths;
+                final float[] ascents = lineBreaks.ascents;
+                final float[] descents = lineBreaks.descents;
+                final int[] flags = lineBreaks.flags;
+
+                final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
+                final boolean ellipsisMayBeApplied = ellipsize != null
+                        && (ellipsize == TextUtils.TruncateAt.END
+                            || (mMaximumVisibleLineCount == 1
+                                    && ellipsize != TextUtils.TruncateAt.MARQUEE));
+                if (0 < remainingLineCount && remainingLineCount < breakCount
+                        && ellipsisMayBeApplied) {
+                    // Calculate width and flag.
+                    float width = 0;
+                    int flag = 0; // XXX May need to also have starting hyphen edit
+                    for (int i = remainingLineCount - 1; i < breakCount; i++) {
+                        if (i == breakCount - 1) {
+                            width += lineWidths[i];
                         } else {
-                            // starts in this paragraph
-
-                            chooseHtv[i] = v;
+                            for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
+                                width += widths[j];
+                            }
                         }
+                        flag |= flags[i] & TAB_MASK;
                     }
-                }
-            }
+                    // Treat the last line and overflowed lines as a single line.
+                    breaks[remainingLineCount - 1] = breaks[breakCount - 1];
+                    lineWidths[remainingLineCount - 1] = width;
+                    flags[remainingLineCount - 1] = flag;
 
-            measured.setPara(source, paraStart, paraEnd, textDir, b);
-            char[] chs = measured.mChars;
-            float[] widths = measured.mWidths;
-            byte[] chdirs = measured.mLevels;
-            int dir = measured.mDir;
-            boolean easy = measured.mEasy;
-
-            // tab stop locations
-            int[] variableTabStops = null;
-            if (spanned != null) {
-                TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
-                        paraEnd, TabStopSpan.class);
-                if (spans.length > 0) {
-                    int[] stops = new int[spans.length];
-                    for (int i = 0; i < spans.length; i++) {
-                        stops[i] = spans[i].getTabStop();
-                    }
-                    Arrays.sort(stops, 0, stops.length);
-                    variableTabStops = stops;
-                }
-            }
-
-            nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
-                    firstWidth, firstWidthLineCount, restWidth,
-                    variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
-                    // TODO: Support more justification mode, e.g. letter spacing, stretching.
-                    b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
-                    // TODO: indents and paddings don't need to get passed to native code for every
-                    // paragraph. Pass them to native code just once.
-                    indents, mLeftPaddings, mRightPaddings, mLineCount);
-
-            // measurement has to be done before performing line breaking
-            // but we don't want to recompute fontmetrics or span ranges the
-            // second time, so we cache those and then use those stored values
-            int fmCacheCount = 0;
-            int spanEndCacheCount = 0;
-            for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
-                if (fmCacheCount * 4 >= fmCache.length) {
-                    int[] grow = new int[fmCacheCount * 4 * 2];
-                    System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
-                    fmCache = grow;
+                    breakCount = remainingLineCount;
                 }
 
-                if (spanEndCacheCount >= spanEndCache.length) {
-                    int[] grow = new int[spanEndCacheCount * 2];
-                    System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
-                    spanEndCache = grow;
-                }
+                // here is the offset of the starting character of the line we are currently
+                // measuring
+                int here = paraStart;
 
-                if (spanned == null) {
-                    spanEnd = paraEnd;
-                    int spanLen = spanEnd - spanStart;
-                    measured.addStyleRun(paint, spanLen, fm);
-                } else {
-                    spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
-                            MetricAffectingSpan.class);
-                    int spanLen = spanEnd - spanStart;
-                    MetricAffectingSpan[] spans =
-                            spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
-                    spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
-                    measured.addStyleRun(paint, spans, spanLen, fm);
-                }
+                int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
+                int fmCacheIndex = 0;
+                int spanEndCacheIndex = 0;
+                int breakIndex = 0;
+                for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+                    // retrieve end of span
+                    spanEnd = spanEndCache[spanEndCacheIndex++];
 
-                // the order of storage here (top, bottom, ascent, descent) has to match the code below
-                // where these values are retrieved
-                fmCache[fmCacheCount * 4 + 0] = fm.top;
-                fmCache[fmCacheCount * 4 + 1] = fm.bottom;
-                fmCache[fmCacheCount * 4 + 2] = fm.ascent;
-                fmCache[fmCacheCount * 4 + 3] = fm.descent;
-                fmCacheCount++;
+                    // retrieve cached metrics, order matches above
+                    fm.top = fmCache[fmCacheIndex * 4 + 0];
+                    fm.bottom = fmCache[fmCacheIndex * 4 + 1];
+                    fm.ascent = fmCache[fmCacheIndex * 4 + 2];
+                    fm.descent = fmCache[fmCacheIndex * 4 + 3];
+                    fmCacheIndex++;
 
-                spanEndCache[spanEndCacheCount] = spanEnd;
-                spanEndCacheCount++;
-            }
-
-            int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
-                    lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags,
-                    lineBreaks.breaks.length, widths);
-
-            final int[] breaks = lineBreaks.breaks;
-            final float[] lineWidths = lineBreaks.widths;
-            final float[] ascents = lineBreaks.ascents;
-            final float[] descents = lineBreaks.descents;
-            final int[] flags = lineBreaks.flags;
-
-            final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
-            final boolean ellipsisMayBeApplied = ellipsize != null
-                    && (ellipsize == TextUtils.TruncateAt.END
-                        || (mMaximumVisibleLineCount == 1
-                                && ellipsize != TextUtils.TruncateAt.MARQUEE));
-            if (0 < remainingLineCount && remainingLineCount < breakCount
-                    && ellipsisMayBeApplied) {
-                // Calculate width and flag.
-                float width = 0;
-                int flag = 0; // XXX May need to also have starting hyphen edit
-                for (int i = remainingLineCount - 1; i < breakCount; i++) {
-                    if (i == breakCount - 1) {
-                        width += lineWidths[i];
-                    } else {
-                        for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
-                            width += widths[j];
-                        }
-                    }
-                    flag |= flags[i] & TAB_MASK;
-                }
-                // Treat the last line and overflowed lines as a single line.
-                breaks[remainingLineCount - 1] = breaks[breakCount - 1];
-                lineWidths[remainingLineCount - 1] = width;
-                flags[remainingLineCount - 1] = flag;
-
-                breakCount = remainingLineCount;
-            }
-
-            // here is the offset of the starting character of the line we are currently measuring
-            int here = paraStart;
-
-            int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
-            int fmCacheIndex = 0;
-            int spanEndCacheIndex = 0;
-            int breakIndex = 0;
-            for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
-                // retrieve end of span
-                spanEnd = spanEndCache[spanEndCacheIndex++];
-
-                // retrieve cached metrics, order matches above
-                fm.top = fmCache[fmCacheIndex * 4 + 0];
-                fm.bottom = fmCache[fmCacheIndex * 4 + 1];
-                fm.ascent = fmCache[fmCacheIndex * 4 + 2];
-                fm.descent = fmCache[fmCacheIndex * 4 + 3];
-                fmCacheIndex++;
-
-                if (fm.top < fmTop) {
-                    fmTop = fm.top;
-                }
-                if (fm.ascent < fmAscent) {
-                    fmAscent = fm.ascent;
-                }
-                if (fm.descent > fmDescent) {
-                    fmDescent = fm.descent;
-                }
-                if (fm.bottom > fmBottom) {
-                    fmBottom = fm.bottom;
-                }
-
-                // skip breaks ending before current span range
-                while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
-                    breakIndex++;
-                }
-
-                while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
-                    int endPos = paraStart + breaks[breakIndex];
-
-                    boolean moreChars = (endPos < bufEnd);
-
-                    final int ascent = fallbackLineSpacing
-                            ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
-                            : fmAscent;
-                    final int descent = fallbackLineSpacing
-                            ? Math.max(fmDescent, Math.round(descents[breakIndex]))
-                            : fmDescent;
-                    v = out(source, here, endPos,
-                            ascent, descent, fmTop, fmBottom,
-                            v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
-                            needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
-                            addLastLineSpacing, chs, widths, paraStart, ellipsize,
-                            ellipsizedWidth, lineWidths[breakIndex], paint, moreChars);
-
-                    if (endPos < spanEnd) {
-                        // preserve metrics for current span
+                    if (fm.top < fmTop) {
                         fmTop = fm.top;
-                        fmBottom = fm.bottom;
+                    }
+                    if (fm.ascent < fmAscent) {
                         fmAscent = fm.ascent;
+                    }
+                    if (fm.descent > fmDescent) {
                         fmDescent = fm.descent;
-                    } else {
-                        fmTop = fmBottom = fmAscent = fmDescent = 0;
+                    }
+                    if (fm.bottom > fmBottom) {
+                        fmBottom = fm.bottom;
                     }
 
-                    here = endPos;
-                    breakIndex++;
-
-                    if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
-                        return;
+                    // skip breaks ending before current span range
+                    while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+                        breakIndex++;
                     }
+
+                    while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
+                        int endPos = paraStart + breaks[breakIndex];
+
+                        boolean moreChars = (endPos < bufEnd);
+
+                        final int ascent = fallbackLineSpacing
+                                ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
+                                : fmAscent;
+                        final int descent = fallbackLineSpacing
+                                ? Math.max(fmDescent, Math.round(descents[breakIndex]))
+                                : fmDescent;
+                        v = out(source, here, endPos,
+                                ascent, descent, fmTop, fmBottom,
+                                v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
+                                flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd,
+                                includepad, trackpad, addLastLineSpacing, chs, widths, paraStart,
+                                ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint,
+                                moreChars);
+
+                        if (endPos < spanEnd) {
+                            // preserve metrics for current span
+                            fmTop = fm.top;
+                            fmBottom = fm.bottom;
+                            fmAscent = fm.ascent;
+                            fmDescent = fm.descent;
+                        } else {
+                            fmTop = fmBottom = fmAscent = fmDescent = 0;
+                        }
+
+                        here = endPos;
+                        breakIndex++;
+
+                        if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
+                            return;
+                        }
+                    }
+                }
+
+                if (paraEnd == bufEnd) {
+                    break;
                 }
             }
 
-            if (paraEnd == bufEnd)
-                break;
-        }
+            if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
+                    && mLineCount < mMaximumVisibleLineCount) {
+                measured.setPara(source, bufEnd, bufEnd, textDir);
 
-        if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
-                mLineCount < mMaximumVisibleLineCount) {
-            measured.setPara(source, bufEnd, bufEnd, textDir, b);
+                paint.getFontMetricsInt(fm);
 
-            paint.getFontMetricsInt(fm);
-
-            v = out(source,
-                    bufEnd, bufEnd, fm.ascent, fm.descent,
-                    fm.top, fm.bottom,
-                    v,
-                    spacingmult, spacingadd, null,
-                    null, fm, 0,
-                    needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
-                    includepad, trackpad, addLastLineSpacing, null,
-                    null, bufStart, ellipsize,
-                    ellipsizedWidth, 0, paint, false);
+                v = out(source,
+                        bufEnd, bufEnd, fm.ascent, fm.descent,
+                        fm.top, fm.bottom,
+                        v,
+                        spacingmult, spacingadd, null,
+                        null, fm, 0,
+                        needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
+                        includepad, trackpad, addLastLineSpacing, null,
+                        null, bufStart, ellipsize,
+                        ellipsizedWidth, 0, paint, false);
+            }
+        } finally {
+            nFinish(nativePtr);
         }
     }
 
@@ -1487,26 +1473,51 @@
                 mMaxLineHeight : super.getHeight();
     }
 
-    private static native long nNewBuilder();
-    private static native void nFreeBuilder(long nativePtr);
-    private static native void nFinishBuilder(long nativePtr);
+    /**
+     * Measurement and break iteration is done in native code. The protocol for using
+     * the native code is as follows.
+     *
+     * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
+     * following:
+     *
+     *   - Call one of the following methods for each run within the paragraph depending on the type
+     *     of run:
+     *     + addStyleRun (a text run, to be measured in native code)
+     *     + addReplacementRun (a replacement run, width is given)
+     *
+     *   - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
+     *
+     * After all paragraphs, call finish() to release expensive buffers.
+     */
 
-    // Set up paragraph text and settings; done as one big method to minimize jni crossings
-    private static native void nSetupParagraph(
-            /* non zero */ long nativePtr, @NonNull char[] text, @IntRange(from = 0) int length,
-            @FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount,
-            @FloatRange(from = 0.0f) float restWidth, @Nullable int[] variableTabStops,
-            int defaultTabStop, @BreakStrategy int breakStrategy,
-            @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
-            @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
-            @IntRange(from = 0) int indentsOffset);
+    /* package */ static void addStyleRun(long nativePtr, TextPaint paint, int start, int end,
+            boolean isRtl) {
+        nAddStyleRun(nativePtr, paint.getNativeInstance(), start, end, isRtl);
+    }
 
-    // TODO: Make this method CriticalNative once native code defers doing layouts.
+    /* package */ static void addReplacementRun(long nativePtr, TextPaint paint, int start, int end,
+            float width) {
+        nAddReplacementRun(nativePtr, paint.getNativeInstance(), start, end, width);
+    }
+
+    @FastNative
+    private static native long nInit(
+            @BreakStrategy int breakStrategy,
+            @HyphenationFrequency int hyphenationFrequency,
+            boolean isJustified,
+            @Nullable int[] indents,
+            @Nullable int[] leftPaddings,
+            @Nullable int[] rightPaddings);
+
+    @CriticalNative
+    private static native void nFinish(long nativePtr);
+
+    @CriticalNative
     private static native void nAddStyleRun(
             /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
             @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl);
 
-    // TODO: Make this method CriticalNative once native code defers doing layouts.
+    @CriticalNative
     private static native void nAddReplacementRun(
             /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
             @IntRange(from = 0) int start, @IntRange(from = 0) int end,
@@ -1519,10 +1530,28 @@
     // arrays do not have to be resized
     // The individual character widths will be returned in charWidths. The length of charWidths must
     // be at least the length of the text.
-    private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
-            int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
-            float[] recycleDescents, int[] recycleFlags, int recycleLength,
-            float[] charWidths);
+    private static native int nComputeLineBreaks(
+            /* non zero */ long nativePtr,
+
+            // Inputs
+            @NonNull char[] text,
+            @IntRange(from = 0) int length,
+            @FloatRange(from = 0.0f) float firstWidth,
+            @IntRange(from = 0) int firstWidthLineCount,
+            @FloatRange(from = 0.0f) float restWidth,
+            @Nullable int[] variableTabStops,
+            int defaultTabStop,
+            @IntRange(from = 0) int indentsOffset,
+
+            // Outputs
+            @NonNull LineBreaks recycle,
+            @IntRange(from  = 0) int recycleLength,
+            @NonNull int[] recycleBreaks,
+            @NonNull float[] recycleWidths,
+            @NonNull float[] recycleAscents,
+            @NonNull float[] recycleDescents,
+            @NonNull int[] recycleFlags,
+            @NonNull float[] charWidths);
 
     private int mLineCount;
     private int mTopPadding, mBottomPadding;
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 20c0ed8..86cc0141 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -28,6 +28,7 @@
 import android.text.style.ReplacementSpan;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
@@ -44,7 +45,8 @@
  *
  * @hide
  */
-class TextLine {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class TextLine {
     private static final boolean DEBUG = false;
 
     private TextPaint mPaint;
@@ -82,7 +84,8 @@
      *
      * @return an uninitialized TextLine
      */
-    static TextLine obtain() {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static TextLine obtain() {
         TextLine tl;
         synchronized (sCached) {
             for (int i = sCached.length; --i >= 0;) {
@@ -107,7 +110,8 @@
      * @return null, as a convenience from clearing references to the provided
      * TextLine
      */
-    static TextLine recycle(TextLine tl) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static TextLine recycle(TextLine tl) {
         tl.mText = null;
         tl.mPaint = null;
         tl.mDirections = null;
@@ -142,7 +146,8 @@
      * @param hasTabs true if the line might contain tabs
      * @param tabStops the tabStops. Can be null.
      */
-    void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
             Directions directions, boolean hasTabs, TabStops tabStops) {
         mPaint = paint;
         mText = text;
@@ -196,7 +201,8 @@
     /**
      * Justify the line to the given width.
      */
-    void justify(float justifyWidth) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void justify(float justifyWidth) {
         int end = mLen;
         while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) {
             end--;
@@ -277,7 +283,8 @@
      * @param fmi receives font metrics information, can be null
      * @return the signed width of the line
      */
-    float metrics(FontMetricsInt fmi) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public float metrics(FontMetricsInt fmi) {
         return measure(mLen, false, fmi);
     }
 
@@ -1165,23 +1172,18 @@
     }
 
     private boolean isStretchableWhitespace(int ch) {
-        // TODO: Support other stretchable whitespace. (Bug: 34013491)
-        return ch == 0x0020 || ch == 0x00A0;
-    }
-
-    private int nextStretchableSpace(int start, int end) {
-        for (int i = start; i < end; i++) {
-            final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart);
-            if (isStretchableWhitespace(c)) return i;
-        }
-        return end;
+        // TODO: Support NBSP and other stretchable whitespace (b/34013491 and b/68204709).
+        return ch == 0x0020;
     }
 
     /* Return the number of spaces in the text line, for the purpose of justification */
     private int countStretchableSpaces(int start, int end) {
         int count = 0;
-        for (int i = start; i < end; i = nextStretchableSpace(i + 1, end)) {
-            count++;
+        for (int i = start; i < end; i++) {
+            final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart);
+            if (isStretchableWhitespace(c)) {
+                count++;
+            }
         }
         return count;
     }
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 68afeec..cbdaa69 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1519,7 +1519,7 @@
                     }
 
                     // XXX this is probably ok, but need to look at it more
-                    tempMt.setPara(format, 0, format.length(), textDir, null);
+                    tempMt.setPara(format, 0, format.length(), textDir);
                     float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null);
 
                     if (w + moreWid <= avail) {
@@ -1541,7 +1541,7 @@
     private static float setPara(MeasuredText mt, TextPaint paint,
             CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
 
-        mt.setPara(text, start, end, textDir, null);
+        mt.setPara(text, start, end, textDir);
 
         float width;
         Spanned sp = text instanceof Spanned ? (Spanned) text : null;
diff --git a/core/java/android/view/TouchDelegate.java b/core/java/android/view/TouchDelegate.java
index cf36f43..dc50fa1 100644
--- a/core/java/android/view/TouchDelegate.java
+++ b/core/java/android/view/TouchDelegate.java
@@ -44,7 +44,7 @@
 
     /**
      * mBounds inflated to include some slop. This rect is to track whether the motion events
-     * should be considered to be be within the delegate view.
+     * should be considered to be within the delegate view.
      */
     private Rect mSlopBounds;
 
@@ -64,14 +64,12 @@
     public static final int BELOW = 2;
 
     /**
-     * The touchable region of the View extends to the left of its
-     * actual extent.
+     * The touchable region of the View extends to the left of its actual extent.
      */
     public static final int TO_LEFT = 4;
 
     /**
-     * The touchable region of the View extends to the right of its
-     * actual extent.
+     * The touchable region of the View extends to the right of its actual extent.
      */
     public static final int TO_RIGHT = 8;
 
@@ -108,28 +106,24 @@
         boolean handled = false;
 
         switch (event.getAction()) {
-        case MotionEvent.ACTION_DOWN:
-            Rect bounds = mBounds;
-
-            if (bounds.contains(x, y)) {
-                mDelegateTargeted = true;
-                sendToDelegate = true;
-            }
-            break;
-        case MotionEvent.ACTION_UP:
-        case MotionEvent.ACTION_MOVE:
-            sendToDelegate = mDelegateTargeted;
-            if (sendToDelegate) {
-                Rect slopBounds = mSlopBounds;
-                if (!slopBounds.contains(x, y)) {
-                    hit = false;
+            case MotionEvent.ACTION_DOWN:
+                mDelegateTargeted = mBounds.contains(x, y);
+                sendToDelegate = mDelegateTargeted;
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_MOVE:
+                sendToDelegate = mDelegateTargeted;
+                if (sendToDelegate) {
+                    Rect slopBounds = mSlopBounds;
+                    if (!slopBounds.contains(x, y)) {
+                        hit = false;
+                    }
                 }
-            }
-            break;
-        case MotionEvent.ACTION_CANCEL:
-            sendToDelegate = mDelegateTargeted;
-            mDelegateTargeted = false;
-            break;
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                sendToDelegate = mDelegateTargeted;
+                mDelegateTargeted = false;
+                break;
         }
         if (sendToDelegate) {
             final View delegateView = mDelegateView;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 9cc1959..f0d05da 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -4055,6 +4055,7 @@
             long deltaUptime = uptimeMs - mLastWakeupUptimeMs;
             SamplingTimer timer = getWakeupReasonTimerLocked(mLastWakeupReason);
             timer.add(deltaUptime * 1000, 1); // time in in microseconds
+            StatsLog.write(StatsLog.KERNEL_WAKEUP_REPORTED, mLastWakeupReason, deltaUptime * 1000);
             mLastWakeupReason = null;
         }
     }
@@ -4633,6 +4634,7 @@
                 if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: "
                         + Integer.toHexString(mHistoryCur.states));
                 newHistory = true;
+                StatsLog.write(StatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin);
             } else {
                 stopAllPhoneSignalStrengthTimersLocked(-1);
             }
@@ -5188,6 +5190,7 @@
             if (strengthBin >= 0) {
                 if (!mWifiSignalStrengthsTimer[strengthBin].isRunningLocked()) {
                     mWifiSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime);
+                    StatsLog.write(StatsLog.WIFI_SIGNAL_STRENGTH_CHANGED, strengthBin);
                 }
                 mHistoryCur.states2 =
                         (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
@@ -6053,6 +6056,8 @@
                             mBsi.mFullWifiLockTimers, mBsi.mOnBatteryTimeBase);
                 }
                 mFullWifiLockTimer.startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, getUid(), 1);
             }
         }
 
@@ -6061,6 +6066,10 @@
             if (mFullWifiLockOut) {
                 mFullWifiLockOut = false;
                 mFullWifiLockTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mFullWifiLockTimer.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, getUid(), 0);
+                }
             }
         }
 
@@ -6074,6 +6083,8 @@
                             mOnBatteryBackgroundTimeBase);
                 }
                 mWifiScanTimer.startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, getUid(), 1);
             }
         }
 
@@ -6082,6 +6093,10 @@
             if (mWifiScanStarted) {
                 mWifiScanStarted = false;
                 mWifiScanTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mWifiScanTimer.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, getUid(), 0);
+                }
             }
         }
 
@@ -8876,6 +8891,8 @@
             Wakelock wl = mWakelockStats.startObject(name);
             if (wl != null) {
                 getWakelockTimerLocked(wl, type).startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Hopefully use a worksource instead of a uid (so move elsewhere)
+                StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 1);
             }
             if (type == WAKE_TYPE_PARTIAL) {
                 createAggregatedPartialWakelockTimerLocked().startRunningLocked(elapsedRealtimeMs);
@@ -8893,7 +8910,12 @@
         public void noteStopWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) {
             Wakelock wl = mWakelockStats.stopObject(name);
             if (wl != null) {
-                getWakelockTimerLocked(wl, type).stopRunningLocked(elapsedRealtimeMs);
+                StopwatchTimer wlt = getWakelockTimerLocked(wl, type);
+                wlt.stopRunningLocked(elapsedRealtimeMs);
+                if (!wlt.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 0);
+                }
             }
             if (type == WAKE_TYPE_PARTIAL) {
                 if (mAggregatedPartialWakelockTimer != null) {
@@ -11156,11 +11178,15 @@
     // This should probably be exposed in the API, though it's not critical
     public static final int BATTERY_PLUGGED_NONE = 0;
 
-    public void setBatteryStateLocked(int status, int health, int plugType, int level,
-            int temp, int volt, int chargeUAh, int chargeFullUAh) {
+    public void setBatteryStateLocked(final int status, final int health, final int plugType,
+            final int level, /* not final */ int temp, final int volt, final int chargeUAh,
+            final int chargeFullUAh) {
         // Temperature is encoded without the signed bit, so clamp any negative temperatures to 0.
         temp = Math.max(0, temp);
 
+        reportChangesToStatsLog(mHaveBatteryLevel ? mHistoryCur : null,
+                status, plugType, level, temp);
+
         final boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
         final long uptime = mClocks.uptimeMillis();
         final long elapsedRealtime = mClocks.elapsedRealtime();
@@ -11337,6 +11363,24 @@
         mMaxLearnedBatteryCapacity = Math.max(mMaxLearnedBatteryCapacity, chargeFullUAh);
     }
 
+    // Inform StatsLog of setBatteryState changes.
+    // If this is the first reporting, pass in recentPast == null.
+    private void reportChangesToStatsLog(HistoryItem recentPast,
+            final int status, final int plugType, final int level, final int temp) {
+
+        if (recentPast == null || recentPast.batteryStatus != status) {
+            StatsLog.write(StatsLog.CHARGING_STATE_CHANGED, status);
+        }
+        if (recentPast == null || recentPast.batteryPlugType != plugType) {
+            StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, plugType);
+        }
+        if (recentPast == null || recentPast.batteryLevel != level) {
+            StatsLog.write(StatsLog.BATTERY_LEVEL_CHANGED, level);
+        }
+        // Let's just always print the temperature, regardless of whether it changed.
+        StatsLog.write(StatsLog.DEVICE_TEMPERATURE_REPORTED, temp);
+    }
+
     public long getAwakeTimeBattery() {
         return computeBatteryUptime(getBatteryUptimeLocked(), STATS_CURRENT);
     }
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index ea4575a..5bddd2f 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -16,9 +16,15 @@
 
 package com.android.internal.os;
 
+import android.annotation.NonNull;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.SystemClock;
 import android.util.EventLog;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.internal.util.Preconditions;
 
 import dalvik.system.VMRuntime;
 
@@ -31,11 +37,14 @@
  * @see IBinder
  */
 public class BinderInternal {
+    private static final String TAG = "BinderInternal";
     static WeakReference<GcWatcher> sGcWatcher
             = new WeakReference<GcWatcher>(new GcWatcher());
     static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
     static Runnable[] sTmpWatchers = new Runnable[1];
     static long sLastGcTime;
+    static final BinderProxyLimitListenerDelegate sBinderProxyLimitListenerDelegate =
+            new BinderProxyLimitListenerDelegate();
 
     static final class GcWatcher {
         @Override
@@ -106,4 +115,96 @@
     static void forceBinderGc() {
         forceGc("Binder");
     }
+
+    /**
+     * Enable/disable Binder Proxy Instance Counting by Uid. While enabled, the set callback will
+     * be called if this process holds too many Binder Proxies on behalf of a Uid.
+     * @param enabled true to enable counting, false to disable
+     */
+    public static final native void nSetBinderProxyCountEnabled(boolean enabled);
+
+    /**
+     * Get the current number of Binder Proxies held for each uid.
+     * @return SparseIntArray mapping uids to the number of Binder Proxies currently held
+     */
+    public static final native SparseIntArray nGetBinderProxyPerUidCounts();
+
+    /**
+     * Get the current number of Binder Proxies held for an individual uid.
+     * @param uid Requested uid for Binder Proxy count
+     * @return int with the number of Binder proxies held for a uid
+     */
+    public static final native int nGetBinderProxyCount(int uid);
+
+    /**
+     * Set the Binder Proxy watermarks. Default high watermark = 2500. Default low watermark = 2000
+     * @param high  The limit at which the BinderProxyListener callback will be called.
+     * @param low   The threshold a binder count must drop below before the callback
+     *              can be called again. (This is to avoid many repeated calls to the
+     *              callback in a brief period of time)
+     */
+    public static final native void nSetBinderProxyCountWatermarks(int high, int low);
+
+    /**
+     * Interface for callback invocation when the Binder Proxy limit is reached. onLimitReached will
+     * be called with the uid of the app causing too many Binder Proxies
+     */
+    public interface BinderProxyLimitListener {
+        public void onLimitReached(int uid);
+    }
+
+    /**
+     * Callback used by native code to trigger a callback in java code. The callback will be
+     * triggered when too many binder proxies from a uid hits the allowed limit.
+     * @param uid The uid of the bad behaving app sending too many binders
+     */
+    public static void binderProxyLimitCallbackFromNative(int uid) {
+       sBinderProxyLimitListenerDelegate.notifyClient(uid);
+    }
+
+    /**
+     * Set a callback to be triggered when a uid's Binder Proxy limit is reached for this process.
+     * @param listener OnLimitReached of listener will be called in the thread provided by handler
+     * @param handler must not be null, callback will be posted through the handler;
+     *
+     */
+    public static void setBinderProxyCountCallback(BinderProxyLimitListener listener,
+            @NonNull Handler handler) {
+        Preconditions.checkNotNull(handler,
+                "Must provide NonNull Handler to setBinderProxyCountCallback when setting "
+                        + "BinderProxyLimitListener");
+        sBinderProxyLimitListenerDelegate.setListener(listener, handler);
+    }
+
+    /**
+     * Clear the Binder Proxy callback
+     */
+    public static void clearBinderProxyCountCallback() {
+        sBinderProxyLimitListenerDelegate.setListener(null, null);
+    }
+
+    static private class BinderProxyLimitListenerDelegate {
+        private BinderProxyLimitListener mBinderProxyLimitListener;
+        private Handler mHandler;
+
+        void setListener(BinderProxyLimitListener listener, Handler handler) {
+            synchronized (this) {
+                mBinderProxyLimitListener = listener;
+                mHandler = handler;
+            }
+        }
+
+        void notifyClient(final int uid) {
+            synchronized (this) {
+                if (mBinderProxyLimitListener != null) {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            mBinderProxyLimitListener.onLimitReached(uid);
+                        }
+                    });
+                }
+            }
+        }
+    }
 }
diff --git a/core/java/com/android/internal/util/LocalLog.java b/core/java/com/android/internal/util/LocalLog.java
index f0e6171..8edb739 100644
--- a/core/java/com/android/internal/util/LocalLog.java
+++ b/core/java/com/android/internal/util/LocalLog.java
@@ -20,6 +20,7 @@
 import java.util.ArrayList;
 
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 /**
  * Helper class for logging serious issues, which also keeps a small
@@ -63,4 +64,16 @@
             return true;
         }
     }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        synchronized (mLines) {
+            for (int i = 0; i < mLines.size(); ++i) {
+                proto.write(LocalLogProto.LINES, mLines.get(i));
+            }
+        }
+
+        proto.end(token);
+    }
 }
diff --git a/core/java/com/android/internal/widget/Magnifier.java b/core/java/com/android/internal/widget/Magnifier.java
index f9e98ea..f6741c3 100644
--- a/core/java/com/android/internal/widget/Magnifier.java
+++ b/core/java/com/android/internal/widget/Magnifier.java
@@ -151,7 +151,6 @@
     /**
      * @return the height of the magnifier window.
      */
-    @NonNull
     public int getHeight() {
         return mWindowHeight;
     }
@@ -159,7 +158,6 @@
     /**
      * @return the width of the magnifier window.
      */
-    @NonNull
     public int getWidth() {
         return mWindowWidth;
     }
@@ -167,7 +165,6 @@
     /**
      * @return the zoom scale of the magnifier.
      */
-    @NonNull
     public float getZoomScale() {
         return mZoomScale;
     }
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index fe14d48..59b761f 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -332,7 +332,7 @@
     env->ReleaseStringUTFChars(serviceNameObj, serviceNameCStr);
 
     // TODO(b/67981006): true /* retry */
-    sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, false /* retry */, false /* getStub */); 
+    sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, false /* retry */, false /* getStub */);
     sp<hardware::IBinder> service = hardware::toBinder<hidl::base::V1_0::IBase>(ret);
 
     if (service == NULL) {
@@ -346,7 +346,8 @@
     return JHwRemoteBinder::NewObject(env, service);
 }
 
-void JHwBinder_native_configureRpcThreadpool(jlong maxThreads, jboolean callerWillJoin) {
+void JHwBinder_native_configureRpcThreadpool(JNIEnv *, jclass,
+        jlong maxThreads, jboolean callerWillJoin) {
     CHECK(maxThreads > 0);
     ProcessState::self()->setThreadPoolConfiguration(maxThreads, callerWillJoin /*callerJoinsPool*/);
 }
@@ -355,7 +356,7 @@
     IPCThreadState::self()->joinThreadPool();
 }
 
-static void JHwBinder_report_sysprop_change(JNIEnv /**env*/, jobject /*clazz*/)
+static void JHwBinder_report_sysprop_change(JNIEnv * /*env*/, jclass /*clazz*/)
 {
     report_sysprop_change();
 }
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index 05bec28..6f9cc22 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -82,45 +82,45 @@
     constexpr int INDIC_MIN_PREFIX = 2;
     constexpr int INDIC_MIN_SUFFIX = 2;
 
-    addHyphenator("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese
-    addHyphenator("be", 2, 2); // Belarusian
-    addHyphenator("bg", 2, 2); // Bulgarian
-    addHyphenator("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Bengali
-    addHyphenator("cu", 1, 2); // Church Slavonic
-    addHyphenator("cy", 2, 3); // Welsh
-    addHyphenator("da", 2, 2); // Danish
-    addHyphenator("de-1901", 2, 2); // German 1901 orthography
-    addHyphenator("de-1996", 2, 2); // German 1996 orthography
-    addHyphenator("de-CH-1901", 2, 2); // Swiss High German 1901 orthography
-    addHyphenator("en-GB", 2, 3); // British English
-    addHyphenator("en-US", 2, 3); // American English
-    addHyphenator("es", 2, 2); // Spanish
-    addHyphenator("et", 2, 3); // Estonian
-    addHyphenator("eu", 2, 2); // Basque
-    addHyphenator("fr", 2, 3); // French
-    addHyphenator("ga", 2, 3); // Irish
-    addHyphenator("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati
-    addHyphenator("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi
-    addHyphenator("hr", 2, 2); // Croatian
-    addHyphenator("hu", 2, 2); // Hungarian
+    addHyphenator("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Assamese
+    addHyphenator("be", 2, 2);  // Belarusian
+    addHyphenator("bg", 2, 2);  // Bulgarian
+    addHyphenator("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Bengali
+    addHyphenator("cu", 1, 2);  // Church Slavonic
+    addHyphenator("cy", 2, 3);  // Welsh
+    addHyphenator("da", 2, 2);  // Danish
+    addHyphenator("de-1901", 2, 2);  // German 1901 orthography
+    addHyphenator("de-1996", 2, 2);  // German 1996 orthography
+    addHyphenator("de-CH-1901", 2, 2);  // Swiss High German 1901 orthography
+    addHyphenator("en-GB", 2, 3);  // British English
+    addHyphenator("en-US", 2, 3);  // American English
+    addHyphenator("es", 2, 2);  // Spanish
+    addHyphenator("et", 2, 3);  // Estonian
+    addHyphenator("eu", 2, 2);  // Basque
+    addHyphenator("fr", 2, 3);  // French
+    addHyphenator("ga", 2, 3);  // Irish
+    addHyphenator("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Gujarati
+    addHyphenator("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Hindi
+    addHyphenator("hr", 2, 2);  // Croatian
+    addHyphenator("hu", 2, 2);  // Hungarian
     // texhyphen sources say Armenian may be (1, 2); but that it needs confirmation.
     // Going with a more conservative value of (2, 2) for now.
-    addHyphenator("hy", 2, 2); // Armenian
-    addHyphenator("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada
-    addHyphenator("la", 2, 2); // Latin
-    addHyphenator("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam
-    addHyphenator("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script
-    addHyphenator("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi
-    addHyphenator("nb", 2, 2); // Norwegian Bokmål
-    addHyphenator("nn", 2, 2); // Norwegian Nynorsk
-    addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya
-    addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi
-    addHyphenator("pt", 2, 3); // Portuguese
-    addHyphenator("sl", 2, 2); // Slovenian
-    addHyphenator("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil
-    addHyphenator("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu
-    addHyphenator("tk", 2, 2); // Turkmen
-    addHyphenator("und-Ethi", 1, 1); // Any language in Ethiopic script
+    addHyphenator("hy", 2, 2);  // Armenian
+    addHyphenator("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Kannada
+    addHyphenator("la", 2, 2);  // Latin
+    addHyphenator("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Malayalam
+    addHyphenator("mn-Cyrl", 2, 2);  // Mongolian in Cyrillic script
+    addHyphenator("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Marathi
+    addHyphenator("nb", 2, 2);  // Norwegian Bokmål
+    addHyphenator("nn", 2, 2);  // Norwegian Nynorsk
+    addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Oriya
+    addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Punjabi
+    addHyphenator("pt", 2, 3);  // Portuguese
+    addHyphenator("sl", 2, 2);  // Slovenian
+    addHyphenator("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Tamil
+    addHyphenator("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Telugu
+    addHyphenator("tk", 2, 2);  // Turkmen
+    addHyphenator("und-Ethi", 1, 1);  // Any language in Ethiopic script
 
     // Following two hyphenators do not have pattern files but there is some special logic based on
     // language.
@@ -130,13 +130,13 @@
     // English locales that fall back to en-US. The data is from CLDR. It's all English locales,
     // minus the locales whose parent is en-001 (from supplementalData.xml, under <parentLocales>).
     // TODO: Figure out how to get this from ICU.
-    addHyphenatorAlias("en-AS", "en-US"); // English (American Samoa)
-    addHyphenatorAlias("en-GU", "en-US"); // English (Guam)
-    addHyphenatorAlias("en-MH", "en-US"); // English (Marshall Islands)
-    addHyphenatorAlias("en-MP", "en-US"); // English (Northern Mariana Islands)
-    addHyphenatorAlias("en-PR", "en-US"); // English (Puerto Rico)
-    addHyphenatorAlias("en-UM", "en-US"); // English (United States Minor Outlying Islands)
-    addHyphenatorAlias("en-VI", "en-US"); // English (Virgin Islands)
+    addHyphenatorAlias("en-AS", "en-US");  // English (American Samoa)
+    addHyphenatorAlias("en-GU", "en-US");  // English (Guam)
+    addHyphenatorAlias("en-MH", "en-US");  // English (Marshall Islands)
+    addHyphenatorAlias("en-MP", "en-US");  // English (Northern Mariana Islands)
+    addHyphenatorAlias("en-PR", "en-US");  // English (Puerto Rico)
+    addHyphenatorAlias("en-UM", "en-US");  // English (United States Minor Outlying Islands)
+    addHyphenatorAlias("en-VI", "en-US");  // English (Virgin Islands)
 
     // All English locales other than those falling back to en-US are mapped to en-GB.
     addHyphenatorAlias("en", "en-GB");
@@ -150,17 +150,28 @@
     addHyphenatorAlias("no", "nb");
 
     // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl.
-    addHyphenatorAlias("mn", "mn-Cyrl"); // Mongolian
+    addHyphenatorAlias("mn", "mn-Cyrl");  // Mongolian
 
     // Fall back to Ethiopic script for languages likely to be written in Ethiopic.
     // Data is from CLDR's likelySubtags.xml.
     // TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags().
-    addHyphenatorAlias("am", "und-Ethi"); // Amharic
-    addHyphenatorAlias("byn", "und-Ethi"); // Blin
-    addHyphenatorAlias("gez", "und-Ethi"); // Geʻez
-    addHyphenatorAlias("ti", "und-Ethi"); // Tigrinya
-    addHyphenatorAlias("wal", "und-Ethi"); // Wolaytta
+    addHyphenatorAlias("am", "und-Ethi");  // Amharic
+    addHyphenatorAlias("byn", "und-Ethi");  // Blin
+    addHyphenatorAlias("gez", "und-Ethi");  // Geʻez
+    addHyphenatorAlias("ti", "und-Ethi");  // Tigrinya
+    addHyphenatorAlias("wal", "und-Ethi");  // Wolaytta
 
+    // Use Hindi as a fallback hyphenator for all languages written in Devanagari, etc. This makes
+    // sense because our Indic patterns are not really linguistic, but script-based.
+    addHyphenatorAlias("und-Beng", "bn");  // Bengali
+    addHyphenatorAlias("und-Deva", "hi");  // Devanagari -> Hindi
+    addHyphenatorAlias("und-Gujr", "gu");  // Gujarati
+    addHyphenatorAlias("und-Guru", "pa");  // Gurmukhi -> Punjabi
+    addHyphenatorAlias("und-Knda", "kn");  // Kannada
+    addHyphenatorAlias("und-Mlym", "ml");  // Malayalam
+    addHyphenatorAlias("und-Orya", "or");  // Oriya
+    addHyphenatorAlias("und-Taml", "ta");  // Tamil
+    addHyphenatorAlias("und-Telu", "te");  // Telugu
 }
 
 static const JNINativeMethod gMethods[] = {
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index 04e9dfd..c1419ba 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -55,11 +55,11 @@
 class JNILineBreakerLineWidth : public minikin::LineBreaker::LineWidthDelegate {
     public:
         JNILineBreakerLineWidth(float firstWidth, int32_t firstLineCount, float restWidth,
-                std::vector<float>&& indents, std::vector<float>&& leftPaddings,
-                std::vector<float>&& rightPaddings, int32_t indentsAndPaddingsOffset)
+                const std::vector<float>& indents, const std::vector<float>& leftPaddings,
+                const std::vector<float>& rightPaddings, int32_t indentsAndPaddingsOffset)
             : mFirstWidth(firstWidth), mFirstLineCount(firstLineCount), mRestWidth(restWidth),
-              mIndents(std::move(indents)), mLeftPaddings(std::move(leftPaddings)),
-              mRightPaddings(std::move(rightPaddings)), mOffset(indentsAndPaddingsOffset) {}
+              mIndents(indents), mLeftPaddings(leftPaddings),
+              mRightPaddings(rightPaddings), mOffset(indentsAndPaddingsOffset) {}
 
         float getLineWidth(size_t lineNo) override {
             const float width = ((ssize_t)lineNo < (ssize_t)mFirstLineCount)
@@ -91,9 +91,9 @@
         const float mFirstWidth;
         const int32_t mFirstLineCount;
         const float mRestWidth;
-        const std::vector<float> mIndents;
-        const std::vector<float> mLeftPaddings;
-        const std::vector<float> mRightPaddings;
+        const std::vector<float>& mIndents;
+        const std::vector<float>& mLeftPaddings;
+        const std::vector<float>& mRightPaddings;
         const int32_t mOffset;
 };
 
@@ -106,32 +106,132 @@
     }
 }
 
+class Run {
+    public:
+        Run(int32_t start, int32_t end) : mStart(start), mEnd(end) {}
+        virtual ~Run() {}
+
+        virtual void addTo(minikin::LineBreaker* lineBreaker) = 0;
+
+    protected:
+        const int32_t mStart;
+        const int32_t mEnd;
+
+    private:
+        // Forbid copy and assign.
+        Run(const Run&) = delete;
+        void operator=(const Run&) = delete;
+};
+
+class StyleRun : public Run {
+    public:
+        StyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
+                std::shared_ptr<minikin::FontCollection>&& collection,
+                minikin::FontStyle&& style, bool isRtl)
+            : Run(start, end), mPaint(std::move(paint)), mCollection(std::move(collection)),
+              mStyle(std::move(style)), mIsRtl(isRtl) {}
+
+        void addTo(minikin::LineBreaker* lineBreaker) override {
+            lineBreaker->addStyleRun(&mPaint, mCollection, mStyle, mStart, mEnd, mIsRtl);
+        }
+
+    private:
+        minikin::MinikinPaint mPaint;
+        std::shared_ptr<minikin::FontCollection> mCollection;
+        minikin::FontStyle mStyle;
+        const bool mIsRtl;
+};
+
+class Replacement : public Run {
+    public:
+        Replacement(int32_t start, int32_t end, float width, uint32_t localeListId)
+            : Run(start, end), mWidth(width), mLocaleListId(localeListId) {}
+
+        void addTo(minikin::LineBreaker* lineBreaker) override {
+            lineBreaker->addReplacement(mStart, mEnd, mWidth, mLocaleListId);
+        }
+
+    private:
+        const float mWidth;
+        const uint32_t mLocaleListId;
+};
+
+class StaticLayoutNative {
+    public:
+        StaticLayoutNative(
+                minikin::BreakStrategy strategy, minikin::HyphenationFrequency frequency,
+                bool isJustified, std::vector<float>&& indents, std::vector<float>&& leftPaddings,
+                std::vector<float>&& rightPaddings)
+            : mStrategy(strategy), mFrequency(frequency), mIsJustified(isJustified),
+              mIndents(std::move(indents)), mLeftPaddings(std::move(leftPaddings)),
+              mRightPaddings(std::move(rightPaddings)) {}
+
+        void addStyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
+                         std::shared_ptr<minikin::FontCollection> collection,
+                         minikin::FontStyle&& style, bool isRtl) {
+            mRuns.emplace_back(std::make_unique<StyleRun>(
+                    start, end, std::move(paint), std::move(collection), std::move(style), isRtl));
+        }
+
+        void addReplacementRun(int32_t start, int32_t end, float width, uint32_t localeListId) {
+            mRuns.emplace_back(std::make_unique<Replacement>(start, end, width, localeListId));
+        }
+
+        // Only valid while this instance is alive.
+        inline std::unique_ptr<minikin::LineBreaker::LineWidthDelegate> buildLineWidthDelegate(
+                float firstWidth, int32_t firstLineCount, float restWidth,
+                int32_t indentsAndPaddingsOffset) {
+            return std::make_unique<JNILineBreakerLineWidth>(
+                firstWidth, firstLineCount, restWidth, mIndents, mLeftPaddings, mRightPaddings,
+                indentsAndPaddingsOffset);
+        }
+
+        void addRuns(minikin::LineBreaker* lineBreaker) {
+            for (const auto& run : mRuns) {
+                run->addTo(lineBreaker);
+            }
+        }
+
+        void clearRuns() {
+            mRuns.clear();
+        }
+
+        inline minikin::BreakStrategy getStrategy() const { return mStrategy; }
+        inline minikin::HyphenationFrequency getFrequency() const { return mFrequency; }
+        inline bool isJustified() const { return mIsJustified; }
+
+    private:
+        const minikin::BreakStrategy mStrategy;
+        const minikin::HyphenationFrequency mFrequency;
+        const bool mIsJustified;
+        const std::vector<float> mIndents;
+        const std::vector<float> mLeftPaddings;
+        const std::vector<float> mRightPaddings;
+
+        std::vector<std::unique_ptr<Run>> mRuns;
+};
+
+static inline StaticLayoutNative* toNative(jlong ptr) {
+    return reinterpret_cast<StaticLayoutNative*>(ptr);
+}
+
 // set text and set a number of parameters for creating a layout (width, tabstops, strategy,
 // hyphenFrequency)
-static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, jint length,
-        jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth,
-        jintArray variableTabStops, jint defaultTabStop, jint strategy, jint hyphenFrequency,
-        jboolean isJustified, jintArray indents, jintArray leftPaddings, jintArray rightPaddings,
-        jint indentsAndPaddingsOffset) {
-    minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
-    b->resize(length);
-    env->GetCharArrayRegion(text, 0, length, b->buffer());
-    b->setText();
-    if (variableTabStops == nullptr) {
-        b->setTabStops(nullptr, 0, defaultTabStop);
-    } else {
-        ScopedIntArrayRO stops(env, variableTabStops);
-        b->setTabStops(stops.get(), stops.size(), defaultTabStop);
-    }
-    b->setStrategy(static_cast<minikin::BreakStrategy>(strategy));
-    b->setHyphenationFrequency(static_cast<minikin::HyphenationFrequency>(hyphenFrequency));
-    b->setJustified(isJustified);
+static jlong nInit(JNIEnv* env, jclass /* unused */,
+        jint breakStrategy, jint hyphenationFrequency, jboolean isJustified,
+        jintArray indents, jintArray leftPaddings, jintArray rightPaddings) {
+    return reinterpret_cast<jlong>(new StaticLayoutNative(
+            static_cast<minikin::BreakStrategy>(breakStrategy),
+            static_cast<minikin::HyphenationFrequency>(hyphenationFrequency),
+            isJustified,
+            jintArrayToFloatVector(env, indents),
+            jintArrayToFloatVector(env, leftPaddings),
+            jintArrayToFloatVector(env, rightPaddings)));
+}
 
-    // TODO: copy indents and paddings only once when LineBreaker is started to be used.
-    b->setLineWidthDelegate(std::make_unique<JNILineBreakerLineWidth>(
-            firstWidth, firstWidthLineLimit, restWidth, jintArrayToFloatVector(env, indents),
-            jintArrayToFloatVector(env, leftPaddings), jintArrayToFloatVector(env, rightPaddings),
-            indentsAndPaddingsOffset));
+// CriticalNative
+static void nFinish(jlong nativePtr) {
+    delete toNative(nativePtr);
 }
 
 static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
@@ -163,42 +263,65 @@
 }
 
 static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
-                               jobject recycle, jintArray recycleBreaks,
-                               jfloatArray recycleWidths, jfloatArray recycleAscents,
-                               jfloatArray recycleDescents, jintArray recycleFlags,
-                               jint recycleLength, jfloatArray charWidths) {
-    minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
+        // Inputs
+        jcharArray text,
+        jint length,
+        jfloat firstWidth,
+        jint firstWidthLineCount,
+        jfloat restWidth,
+        jintArray variableTabStops,
+        jint defaultTabStop,
+        jint indentsOffset,
 
-    size_t nBreaks = b->computeBreaks();
+        // Outputs
+        jobject recycle,
+        jint recycleLength,
+        jintArray recycleBreaks,
+        jfloatArray recycleWidths,
+        jfloatArray recycleAscents,
+        jfloatArray recycleDescents,
+        jintArray recycleFlags,
+        jfloatArray charWidths) {
+
+    StaticLayoutNative* builder = toNative(nativePtr);
+
+    // TODO: Reorganize minikin APIs.
+    minikin::LineBreaker b;
+    b.resize(length);
+    env->GetCharArrayRegion(text, 0, length, b.buffer());
+    b.setText();
+    if (variableTabStops == nullptr) {
+        b.setTabStops(nullptr, 0, defaultTabStop);
+    } else {
+        ScopedIntArrayRO stops(env, variableTabStops);
+        b.setTabStops(stops.get(), stops.size(), defaultTabStop);
+    }
+    b.setStrategy(builder->getStrategy());
+    b.setHyphenationFrequency(builder->getFrequency());
+    b.setJustified(builder->isJustified());
+    b.setLineWidthDelegate(builder->buildLineWidthDelegate(
+            firstWidth, firstWidthLineCount, restWidth, indentsOffset));
+
+    builder->addRuns(&b);
+
+    size_t nBreaks = b.computeBreaks();
 
     recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents,
-            recycleFlags, recycleLength, nBreaks, b->getBreaks(), b->getWidths(), b->getAscents(),
-            b->getDescents(), b->getFlags());
+            recycleFlags, recycleLength, nBreaks, b.getBreaks(), b.getWidths(), b.getAscents(),
+            b.getDescents(), b.getFlags());
 
-    env->SetFloatArrayRegion(charWidths, 0, b->size(), b->charWidths());
+    env->SetFloatArrayRegion(charWidths, 0, b.size(), b.charWidths());
 
-    b->finish();
+    b.finish();
+    builder->clearRuns();
 
     return static_cast<jint>(nBreaks);
 }
 
-static jlong nNewBuilder(JNIEnv*, jclass) {
-    return reinterpret_cast<jlong>(new minikin::LineBreaker);
-}
-
-static void nFreeBuilder(JNIEnv*, jclass, jlong nativePtr) {
-    delete reinterpret_cast<minikin::LineBreaker*>(nativePtr);
-}
-
-static void nFinishBuilder(JNIEnv*, jclass, jlong nativePtr) {
-    minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
-    b->finish();
-}
-
 // Basically similar to Paint.getTextRunAdvances but with C++ interface
-static void nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint, jint start,
-        jint end, jboolean isRtl) {
-    minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
+// CriticalNative
+static void nAddStyleRun(jlong nativePtr, jlong nativePaint, jint start, jint end, jboolean isRtl) {
+    StaticLayoutNative* builder = toNative(nativePtr);
     Paint* paint = reinterpret_cast<Paint*>(nativePaint);
     const Typeface* typeface = paint->getAndroidTypeface();
     minikin::MinikinPaint minikinPaint;
@@ -206,26 +329,59 @@
     minikin::FontStyle style = MinikinUtils::prepareMinikinPaint(&minikinPaint, paint,
             typeface);
 
-    b->addStyleRun(&minikinPaint, resolvedTypeface->fFontCollection, style, start, end, isRtl);
+    builder->addStyleRun(
+        start, end, std::move(minikinPaint), resolvedTypeface->fFontCollection, std::move(style),
+        isRtl);
 }
 
-static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint,
-        jint start, jint end, jfloat width) {
-    minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
+// CriticalNative
+static void nAddReplacementRun(jlong nativePtr, jlong nativePaint, jint start, jint end,
+        jfloat width) {
+    StaticLayoutNative* builder = toNative(nativePtr);
     Paint* paint = reinterpret_cast<Paint*>(nativePaint);
-    b->addReplacement(start, end, width, paint->getMinikinLangListId());
+    builder->addReplacementRun(start, end, width, paint->getMinikinLangListId());
 }
 
 static const JNINativeMethod gMethods[] = {
-    // TODO performance: many of these are candidates for fast jni, awaiting guidance
-    {"nNewBuilder", "()J", (void*) nNewBuilder},
-    {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
-    {"nFinishBuilder", "(J)V", (void*) nFinishBuilder},
-    {"nSetupParagraph", "(J[CIFIF[IIIIZ[I[I[II)V", (void*) nSetupParagraph},
+    // Fast Natives
+    {"nInit", "("
+        "I"  // breakStrategy
+        "I"  // hyphenationFrequency
+        "Z"  // isJustified
+        "[I"  // indents
+        "[I"  // left paddings
+        "[I"  // right paddings
+        ")J", (void*) nInit},
+
+    // Critical Natives
+    {"nFinish", "(J)V", (void*) nFinish},
     {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
     {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
-    {"nComputeLineBreaks", "(JLandroid/text/StaticLayout$LineBreaks;[I[F[F[F[II[F)I",
-        (void*) nComputeLineBreaks}
+
+    // Regular JNI
+    {"nComputeLineBreaks", "("
+        "J"  // nativePtr
+
+        // Inputs
+        "[C"  // text
+        "I"  // length
+        "F"  // firstWidth
+        "I"  // firstWidthLineCount
+        "F"  // restWidth
+        "[I"  // variableTabStops
+        "I"  // defaultTabStop
+        "I"  // indentsOffset
+
+        // Outputs
+        "Landroid/text/StaticLayout$LineBreaks;"  // recycle
+        "I"  // recycleLength
+        "[I"  // recycleBreaks
+        "[F"  // recycleWidths
+        "[F"  // recycleAscents
+        "[F"  // recycleDescents
+        "[I"  // recycleFlags
+        "[F"  // charWidths
+        ")I", (void*) nComputeLineBreaks}
 };
 
 int register_android_text_StaticLayout(JNIEnv* env)
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 7908c9d..6362bc7 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -32,6 +32,7 @@
 #include <binder/IServiceManager.h>
 #include <binder/IPCThreadState.h>
 #include <binder/Parcel.h>
+#include <binder/BpBinder.h>
 #include <binder/ProcessState.h>
 #include <log/log.h>
 #include <utils/Atomic.h>
@@ -80,9 +81,17 @@
     // Class state.
     jclass mClass;
     jmethodID mForceGc;
+    jmethodID mProxyLimitCallback;
 
 } gBinderInternalOffsets;
 
+static struct sparseintarray_offsets_t
+{
+    jclass classObject;
+    jmethodID constructor;
+    jmethodID put;
+} gSparseIntArrayOffsets;
+
 // ----------------------------------------------------------------------------
 
 static struct error_offsets_t
@@ -973,6 +982,43 @@
     android_atomic_and(0, &gNumRefsCreated);
 }
 
+static void android_os_BinderInternal_proxyLimitcallback(int uid)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    env->CallStaticVoidMethod(gBinderInternalOffsets.mClass,
+                              gBinderInternalOffsets.mProxyLimitCallback,
+                              uid);
+}
+
+static void android_os_BinderInternal_setBinderProxyCountEnabled(JNIEnv* env, jobject clazz,
+                                                                 jboolean enable)
+{
+    BpBinder::setCountByUidEnabled((bool) enable);
+}
+
+static jobject android_os_BinderInternal_getBinderProxyPerUidCounts(JNIEnv* env, jclass clazz)
+{
+    Vector<uint32_t> uids, counts;
+    BpBinder::getCountByUid(uids, counts);
+    jobject sparseIntArray = env->NewObject(gSparseIntArrayOffsets.classObject,
+                                            gSparseIntArrayOffsets.constructor);
+    for (size_t i = 0; i < uids.size(); i++) {
+        env->CallVoidMethod(sparseIntArray, gSparseIntArrayOffsets.put,
+                            static_cast<jint>(uids[i]), static_cast<jint>(counts[i]));
+    }
+    return sparseIntArray;
+}
+
+static jint android_os_BinderInternal_getBinderProxyCount(JNIEnv* env, jobject clazz, jint uid) {
+    return static_cast<jint>(BpBinder::getBinderProxyCount(static_cast<uint32_t>(uid)));
+}
+
+static void android_os_BinderInternal_setBinderProxyCountWatermarks(JNIEnv* env, jobject clazz,
+                                                                    jint high, jint low)
+{
+    BpBinder::setBinderProxyCountWatermarks(high, low);
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gBinderInternalMethods[] = {
@@ -981,7 +1027,11 @@
     { "joinThreadPool", "()V", (void*)android_os_BinderInternal_joinThreadPool },
     { "disableBackgroundScheduling", "(Z)V", (void*)android_os_BinderInternal_disableBackgroundScheduling },
     { "setMaxThreads", "(I)V", (void*)android_os_BinderInternal_setMaxThreads },
-    { "handleGc", "()V", (void*)android_os_BinderInternal_handleGc }
+    { "handleGc", "()V", (void*)android_os_BinderInternal_handleGc },
+    { "nSetBinderProxyCountEnabled", "(Z)V", (void*)android_os_BinderInternal_setBinderProxyCountEnabled },
+    { "nGetBinderProxyPerUidCounts", "()Landroid/util/SparseIntArray;", (void*)android_os_BinderInternal_getBinderProxyPerUidCounts },
+    { "nGetBinderProxyCount", "(I)I", (void*)android_os_BinderInternal_getBinderProxyCount },
+    { "nSetBinderProxyCountWatermarks", "(II)V", (void*)android_os_BinderInternal_setBinderProxyCountWatermarks}
 };
 
 const char* const kBinderInternalPathName = "com/android/internal/os/BinderInternal";
@@ -992,6 +1042,16 @@
 
     gBinderInternalOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
     gBinderInternalOffsets.mForceGc = GetStaticMethodIDOrDie(env, clazz, "forceBinderGc", "()V");
+    gBinderInternalOffsets.mProxyLimitCallback = GetStaticMethodIDOrDie(env, clazz, "binderProxyLimitCallbackFromNative", "(I)V");
+
+    jclass SparseIntArrayClass = FindClassOrDie(env, "android/util/SparseIntArray");
+    gSparseIntArrayOffsets.classObject = MakeGlobalRefOrDie(env, SparseIntArrayClass);
+    gSparseIntArrayOffsets.constructor = GetMethodIDOrDie(env, gSparseIntArrayOffsets.classObject,
+                                                           "<init>", "()V");
+    gSparseIntArrayOffsets.put = GetMethodIDOrDie(env, gSparseIntArrayOffsets.classObject, "put",
+                                                   "(II)V");
+
+    BpBinder::setLimitCallback(android_os_BinderInternal_proxyLimitcallback);
 
     return RegisterMethodsOrDie(
         env, kBinderInternalPathName,
diff --git a/core/proto/android/app/alarmmanager.proto b/core/proto/android/app/alarmmanager.proto
new file mode 100644
index 0000000..789e3d6
--- /dev/null
+++ b/core/proto/android/app/alarmmanager.proto
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/app/pendingintent.proto";
+
+option java_multiple_files = true;
+
+package android.app;
+
+/**
+ * An android.app.AlarmManager object.
+ */
+message AlarmManagerProto {
+  enum AlarmType {
+    // Alarm time in System.currentTimeMillis() (wall clock time in UTC), which
+    // will wake up the device when it goes off.
+    RTC_WAKEUP = 0;
+    // Alarm time in System.currentTimeMillis() (wall clock time in UTC).  This
+    // alarm does not wake the device up; if it goes off while the device is
+    // asleep, it will not be delivered until the next time the device wakes up.
+    RTC = 1;
+    // Alarm time in SystemClock.elapsedRealtime() (time since boot, including
+    // sleep), which will wake up the device when it goes off.
+    ELAPSED_REALTIME_WAKEUP = 2;
+    // Alarm time in SystemClock.elapsedRealtime() (time since boot, including
+    // sleep). This alarm does not wake the device up; if it goes off while the
+    // device is asleep, it will not be delivered until the next time the device
+    // wakes up.
+    ELAPSED_REALTIME = 3;
+  }
+}
+
+// An android.app.AlarmManager.AlarmClockInfo object.
+message AlarmClockInfoProto {
+  // This value is UTC wall clock time in milliseconds, as returned by
+  // System#currentTimeMillis() for example.
+  optional int64 trigger_time_ms = 1;
+  optional android.app.PendingIntentProto show_intent = 2;
+}
diff --git a/core/proto/android/app/pendingintent.proto b/core/proto/android/app/pendingintent.proto
new file mode 100644
index 0000000..b562c0b
--- /dev/null
+++ b/core/proto/android/app/pendingintent.proto
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+option java_multiple_files = true;
+
+package android.app;
+
+/**
+ * An android.app.PendingIntent object.
+ */
+message PendingIntentProto {
+  optional string target = 1;
+}
diff --git a/core/proto/android/internal/locallog.proto b/core/proto/android/internal/locallog.proto
new file mode 100644
index 0000000..51f6c1c
--- /dev/null
+++ b/core/proto/android/internal/locallog.proto
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package com.android.internal.util;
+
+option java_multiple_files = true;
+
+message LocalLogProto {
+  repeated string lines = 1;
+}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 5a5454e..0887f03 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -32,6 +32,7 @@
 import "frameworks/base/core/proto/android/service/print.proto";
 import "frameworks/base/core/proto/android/service/procstats.proto";
 import "frameworks/base/core/proto/android/server/activitymanagerservice.proto";
+import "frameworks/base/core/proto/android/server/alarmmanagerservice.proto";
 import "frameworks/base/core/proto/android/providers/settings.proto";
 import "frameworks/base/core/proto/android/os/incidentheader.proto";
 import "frameworks/base/core/proto/android/os/kernelwake.proto";
@@ -125,4 +126,9 @@
 
     optional com.android.server.am.proto.ServiceProto amservices = 3014;
     optional com.android.server.am.proto.ProcessProto amprocesses = 3015;
+
+    optional com.android.server.AlarmManagerServiceProto alarm = 3016 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "alarm --proto"
+    ];
 }
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 788ac8f..c57cb72 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -69,12 +69,11 @@
   optional string real_activity = 6;
   optional string orig_activity = 7;
   optional int32 activity_type = 8;
-  optional int32 return_to_type = 9;
-  optional int32 resize_mode = 10;
-  optional bool fullscreen = 11;
-  optional .android.graphics.RectProto bounds = 12;
-  optional int32 min_width = 13;
-  optional int32 min_height = 14;
+  optional int32 resize_mode = 9;
+  optional bool fullscreen = 10;
+  optional .android.graphics.RectProto bounds = 11;
+  optional int32 min_width = 12;
+  optional int32 min_height = 13;
 }
 
 message ActivityRecordProto {
diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto
new file mode 100644
index 0000000..d2cd190
--- /dev/null
+++ b/core/proto/android/server/alarmmanagerservice.proto
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/app/alarmmanager.proto";
+import "frameworks/base/core/proto/android/app/pendingintent.proto";
+import "frameworks/base/core/proto/android/internal/locallog.proto";
+import "frameworks/base/core/proto/android/os/worksource.proto";
+
+package com.android.server;
+
+option java_multiple_files = true;
+
+message AlarmManagerServiceProto {
+  optional int64 current_time = 1;
+  optional int64 elapsed_realtime = 2;
+  optional int64 last_time_change_clock_time = 3;
+  optional int64 last_time_change_realtime = 4;
+  // Current settings
+  optional ConstantsProto settings = 5;
+  // UIDs currently in the foreground.
+  repeated int32 foreground_uids = 6;
+  // Packages forced into app standby.
+  repeated string forced_app_standby_packages = 7;
+
+  optional bool is_interactive = 8;
+  // Only valid if is_interactive is false.
+  optional int64 time_since_non_interactive_ms = 9;
+  // Only valid if is_interactive is false.
+  optional int64 max_wakeup_delay_ms = 10;
+  // Only valid if is_interactive is false.
+  optional int64 time_since_last_dispatch_ms = 11;
+  // Only valid if is_interactive is false.
+  optional int64 time_until_next_non_wakeup_delivery_ms = 12;
+
+  optional int64 time_until_next_non_wakeup_alarm_ms = 13;
+  optional int64 time_until_next_wakeup_ms = 14;
+  optional int64 time_since_last_wakeup_ms = 15;
+  // Time since the last wakeup was set.
+  optional int64 time_since_last_wakeup_set_ms = 16;
+  optional int64 time_change_event_count = 17;
+  // The current set of user whitelisted apps for device idle mode, meaning
+  // these are allowed to freely schedule alarms. These are app IDs, not UIDs.
+  repeated int32 device_idle_user_whitelist_app_ids = 18;
+
+  repeated AlarmClockMetadataProto next_alarm_clock_metadata = 19;
+
+  repeated BatchProto pending_alarm_batches = 20;
+
+  // List of alarms per uid deferred due to user applied background restrictions
+  // on the source app.
+  repeated AlarmProto pending_user_blocked_background_alarms = 21;
+
+  // When idling mode will end. Will be empty if the device is not currently
+  // idling.
+  optional AlarmProto pending_idle_until = 22;
+
+  // Any alarms that we don't want to run during idle mode. Will be empty if the
+  // device is not currently idling.
+  repeated AlarmProto pending_while_idle_alarms = 23;
+
+  // This is a special alarm that will put the system into idle until it goes
+  // off. The caller has given the time they want this to happen at.
+  optional AlarmProto next_wake_from_idle = 24;
+
+  repeated AlarmProto past_due_non_wakeup_alarms = 25;
+
+  // Number of delayed alarms.
+  optional int32 delayed_alarm_count = 26;
+  // The total amount of time alarms had been delayed. Overlapping alarms are
+  // only counted once (ie. If two alarms were meant to trigger at the same time
+  // but were delayed by 5 seconds, the total time would be 5 seconds).
+  optional int64 total_delay_time_ms = 27;
+  optional int64 max_delay_duration_ms = 28;
+  optional int64 max_non_interactive_duration_ms = 29;
+
+  optional int32 broadcast_ref_count = 30;
+  // Canonical count of (operation.send() - onSendFinished()) and listener
+  // send/complete/timeout invocations.
+  optional int32 pending_intent_send_count = 31;
+  optional int32 pending_intent_finish_count = 32;
+  optional int32 listener_send_count = 33;
+  optional int32 listener_finish_count = 34;
+
+  repeated InFlightProto outstanding_deliveries = 35;
+
+  // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling. It
+  // should be either CosntantsProto.allow_while_idle_short_duration_ms or
+  // ConstantsProto.allow_while_idle_long_duration_ms.
+  optional int64 allow_while_idle_min_duration_ms = 36;
+
+  message LastAllowWhileIdleDispatch {
+    optional int32 uid = 1;
+    // In the 'elapsed' timebase.
+    optional int64 time_ms = 2;
+  }
+  // For each uid, this is the last time we dispatched an "allow while idle"
+  // alarm, used to determine the earliest we can dispatch the next such alarm.
+  repeated LastAllowWhileIdleDispatch last_allow_while_idle_dispatch_times = 37;
+
+  optional com.android.internal.util.LocalLogProto recent_problems = 38;
+
+  message TopAlarm {
+    optional int32 uid = 1;
+    optional string package_name = 2;
+    optional FilterStatsProto filter = 3;
+  }
+  repeated TopAlarm top_alarms = 39;
+
+  message AlarmStat {
+    optional BroadcastStatsProto broadcast = 1;
+    repeated FilterStatsProto filters = 2;
+  }
+  repeated AlarmStat alarm_stats = 40;
+
+  repeated IdleDispatchEntryProto allow_while_idle_dispatches = 41;
+  repeated WakeupEventProto recent_wakeup_history = 42;
+}
+
+// This is a soft wrapper for alarm clock information. It is not representative
+// of an android.app.AlarmManager.AlarmClockInfo object.
+message AlarmClockMetadataProto {
+  optional int32 user = 1;
+  optional bool is_pending_send = 2;
+  // This value is UTC wall clock time in milliseconds, as returned by
+  // System#currentTimeMillis() for example.
+  optional int64 trigger_time_ms = 3;
+}
+
+// A com.android.server.AlarmManagerService.Alarm object.
+message AlarmProto {
+  optional string tag = 1;
+  optional .android.app.AlarmManagerProto.AlarmType type = 2;
+  // How long until the alarm goes off, in the 'elapsed' timebase.
+  optional int64 when_elapsed_ms = 3;
+  optional int64 window_length_ms = 4;
+  optional int64 repeat_interval_ms = 5;
+  optional int32 count = 6;
+  optional int32 flags = 7;
+  optional .android.app.AlarmClockInfoProto alarm_clock = 8;
+  optional .android.app.PendingIntentProto operation = 9;
+  optional string listener = 10;
+}
+
+// A com.android.server.AlarmManagerService.Batch object.
+message BatchProto {
+  // Start time in terms of elapsed realtime.
+  optional int64 start_realtime = 1;
+  // End time in terms of elapsed realtime.
+  optional int64 end_realtime = 2;
+  optional int32 flags = 3;
+  repeated AlarmProto alarms = 4;
+}
+
+// A com.android.server.AlarmManagerService.BroadcastStats object.
+message BroadcastStatsProto {
+  optional int32 uid = 1;
+  optional string package_name = 2;
+  // The total amount of time this broadcast was in flight.
+  optional int64 total_flight_duration_ms = 3;
+  optional int32 count = 4;
+  optional int32 wakeup_count = 5;
+  // The last time this first became active (when nesting changed from 0 to 1)
+  // in terms of elapsed realtime.
+  optional int64 start_time_realtime = 6;
+  // The broadcast is active if nesting > 0.
+  optional int32 nesting = 7;
+}
+
+// A com.android.server.AlarmManagerService.Constants object.
+message ConstantsProto {
+  // Minimum futurity of a new alarm.
+  optional int64 min_futurity_duration_ms = 1;
+  // Minimum alarm recurrence interval.
+  optional int64 min_interval_duration_ms = 2;
+  // Direct alarm listener callback timeout.
+  optional int64 listener_timeout_duration_ms = 3;
+  // Minimum time between ALLOW_WHILE_IDLE alarms when system is not idle.
+  optional int64 allow_while_idle_short_duration_ms = 4;
+  // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling.
+  optional int64 allow_while_idle_long_duration_ms = 5;
+  // BroadcastOptions.setTemporaryAppWhitelistDuration() to use for FLAG_ALLOW_WHILE_IDLE.
+  optional int64 allow_while_idle_whitelist_duration_ms = 6;
+}
+
+// A com.android.server.AlarmManagerService.FilterStats object.
+message FilterStatsProto {
+  optional string tag = 1;
+  // The last time this filter when in flight, in terms of elapsed realtime.
+  optional int64 last_flight_time_realtime = 2;
+  // The total amount of time this filter was in flight.
+  optional int64 total_flight_duration_ms = 3;
+  optional int32 count = 4;
+  optional int32 wakeup_count = 5;
+  // The last time this first became active (when nesting changed from 0 to 1)
+  // in terms of elapsed realtime.
+  optional int64 start_time_realtime = 6;
+  // The filter is active if nesting > 0.
+  optional int32 nesting = 7;
+}
+
+// A com.android.server.AlarmManagerService.IdleDispatchEntry object.
+message IdleDispatchEntryProto {
+  optional int32 uid = 1;
+  optional string pkg = 2;
+  optional string tag = 3;
+  optional string op = 4;
+  // Time when this entry was created, in terms of elapsed realtime.
+  optional int64 entry_creation_realtime = 5;
+  // For a RESCHEDULED op, this is the last time we dispatched an "allow while
+  // idle" alarm for the UID. For a SET op, this is when the alarm was
+  // triggered. Times are in the 'elapsed' timebase.
+  optional int64 arg_realtime = 6;
+}
+
+// A com.android.server.AlarmManagerService.InFlight object.
+message InFlightProto {
+  optional int32 uid = 1;
+  optional string tag = 2;
+  optional int64 when_elapsed_ms = 3;
+  optional .android.app.AlarmManagerProto.AlarmType alarm_type = 4;
+  optional .android.app.PendingIntentProto pending_intent = 5;
+  optional BroadcastStatsProto broadcast_stats = 6;
+  optional FilterStatsProto filter_stats = 7;
+  optional .android.os.WorkSourceProto work_source = 8;
+}
+
+// A com.android.server.AlarmManagerService.WakeupEvent object.
+message WakeupEventProto {
+  optional int32 uid = 1;
+  optional string action = 2;
+  optional int64 when = 3;
+}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5a497ac..f5b391e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3654,7 +3654,7 @@
     </declare-styleable>
 
     <!-- Specify one or more <code>t3tPmm-filter</code> elements inside a
-         <code>host-nfcf-service</code> element to specify a LF_T3T_PMM -->
+         <code>host-nfcf-service</code> element to specify a LF_T3T_PMM. -->
     <declare-styleable name="T3tPmmFilter">
         <attr name="name" />
 
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 15eab1f..f2eb872 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -19,7 +19,12 @@
 	$(call all-java-files-under, src) \
 	$(call all-Iaidl-files-under, src) \
 	$(call all-java-files-under, DisabledTestApp/src) \
-	$(call all-java-files-under, EnabledTestApp/src)
+	$(call all-java-files-under, EnabledTestApp/src) \
+	$(call all-java-files-under, BinderProxyCountingTestApp/src) \
+	$(call all-java-files-under, BinderProxyCountingTestService/src) \
+	$(call all-Iaidl-files-under, aidl)
+
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl
 
 LOCAL_DX_FLAGS := --core-library
 LOCAL_JACK_FLAGS := --multi-dex native
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 9c0543b..51bfc20 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -86,6 +86,7 @@
     <uses-permission android:name="android.permission.BROADCAST_STICKY" />
 
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+    <uses-permission android:name="android.permission.KILL_UID" />
 
     <!-- location test permissions -->
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
diff --git a/core/tests/coretests/BinderProxyCountingTestApp/Android.mk b/core/tests/coretests/BinderProxyCountingTestApp/Android.mk
new file mode 100644
index 0000000..e31d50f
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestApp/Android.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := coretests-aidl
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := BinderProxyCountingTestApp
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml b/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..a971730
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.coretests.binderproxycountingtestapp">
+
+    <application>
+        <service android:name=".BpcTestAppCmdService"
+                 android:exported="true"/>
+    </application>
+</manifest>
diff --git a/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java b/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java
new file mode 100644
index 0000000..5aae1203
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.binderproxycountingtestapp;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.frameworks.coretests.aidl.IBinderProxyCountingService;
+import com.android.frameworks.coretests.aidl.IBpcTestAppCmdService;
+import com.android.frameworks.coretests.aidl.ITestRemoteCallback;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class BpcTestAppCmdService extends Service {
+    private static final String TAG = BpcTestAppCmdService.class.getSimpleName();
+
+    private static final String TEST_SERVICE_PKG =
+            "com.android.frameworks.coretests.binderproxycountingtestservice";
+    private static final String TEST_SERVICE_CLASS =
+            TEST_SERVICE_PKG + ".BinderProxyCountingService";
+    private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
+
+    private static ServiceConnection mServiceConnection;
+    private static IBinderProxyCountingService mBpcService;
+
+    private IBpcTestAppCmdService.Stub mBinder = new IBpcTestAppCmdService.Stub() {
+
+        private ArrayList<BroadcastReceiver> mBrList = new ArrayList();
+        private ArrayList<ITestRemoteCallback> mTrcList = new ArrayList();
+
+        @Override
+        public void createSystemBinders(int count) {
+            int i = 0;
+            while (i++ < count) {
+                BroadcastReceiver br = new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+
+                    }
+                };
+                IntentFilter filt = new IntentFilter(Intent.ACTION_POWER_DISCONNECTED);
+                synchronized (mBrList) {
+                    mBrList.add(br);
+                }
+                registerReceiver(br, filt);
+            }
+        }
+
+        @Override
+        public void releaseSystemBinders(int count) {
+            int i = 0;
+            while (i++ < count) {
+                BroadcastReceiver br;
+                synchronized (mBrList) {
+                    br = mBrList.remove(0);
+                }
+                unregisterReceiver(br);
+            }
+        }
+
+        @Override
+        public void createTestBinders(int count) {
+            int i = 0;
+            while (i++ < count) {
+                ITestRemoteCallback cb = new ITestRemoteCallback.Stub() {};
+                synchronized (mTrcList) {
+                    mTrcList.add(cb);
+                }
+                try {
+                    mBpcService.registerCallback(cb);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException caught! " + e);
+                }
+            }
+        }
+
+        @Override
+        public void releaseTestBinders(int count) {
+            int i = 0;
+            while (i++ < count) {
+
+                ITestRemoteCallback cb;
+                synchronized (mTrcList) {
+                    cb = mTrcList.remove(0);
+                }
+                try {
+                    mBpcService.unregisterCallback(cb);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException caught! " + e);
+                }
+            }
+        }
+
+        @Override
+        public void releaseAllBinders() {
+            synchronized (mBrList) {
+                while (mBrList.size() > 0) {
+                    unregisterReceiver(mBrList.remove(0));
+                }
+            }
+            synchronized (mTrcList) {
+                while (mTrcList.size() > 0) {
+                    try {
+                        mBpcService.unregisterCallback(mTrcList.remove(0));
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "RemoteException caught! " + e);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public String bindToTestService() {
+            try {
+                final CountDownLatch bindLatch = new CountDownLatch(1);
+                mServiceConnection = new ServiceConnection() {
+                    @Override
+                    public void onServiceConnected(ComponentName name, IBinder service) {
+                        Log.i(TAG, "Service connected");
+                        mBpcService = IBinderProxyCountingService.Stub.asInterface(service);
+                        bindLatch.countDown();
+                    }
+
+                    @Override
+                    public void onServiceDisconnected(ComponentName name) {
+                        Log.i(TAG, "Service disconnected");
+                    }
+                };
+                final Intent intent = new Intent()
+                        .setComponent(new ComponentName(TEST_SERVICE_PKG, TEST_SERVICE_CLASS));
+                bindService(intent, mServiceConnection,
+                        Context.BIND_AUTO_CREATE
+                                | Context.BIND_ALLOW_OOM_MANAGEMENT
+                                | Context.BIND_NOT_FOREGROUND);
+                if (!bindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                    throw new RuntimeException("Failed to bind to " + TEST_SERVICE_CLASS);
+                }
+            } catch (Exception e) {
+                unbindFromTestService();
+                Log.e(TAG, e.toString());
+                return e.toString();
+            }
+            return null;
+        }
+
+        @Override
+        public void unbindFromTestService() {
+            if (mBpcService != null) {
+                unbindService(mServiceConnection);
+            }
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/BinderProxyCountingTestService/Android.mk b/core/tests/coretests/BinderProxyCountingTestService/Android.mk
new file mode 100644
index 0000000..a63cf0e
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestService/Android.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := coretests-aidl
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := BinderProxyCountingTestService
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/BinderProxyCountingTestService/AndroidManifest.xml b/core/tests/coretests/BinderProxyCountingTestService/AndroidManifest.xml
new file mode 100644
index 0000000..777bd20
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestService/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.coretests.binderproxycountingtestservice">
+
+    <application>
+        <service android:name=".BpcTestServiceCmdService"
+                 android:exported="true" />
+        <service android:name=".BinderProxyCountingService"
+                 android:exported="true" />
+    </application>
+</manifest>
diff --git a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java
new file mode 100644
index 0000000..41b4c69
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.binderproxycountingtestservice;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+
+import com.android.frameworks.coretests.aidl.IBinderProxyCountingService;
+import com.android.frameworks.coretests.aidl.ITestRemoteCallback;
+
+public class BinderProxyCountingService extends Service {
+    private static final String TAG = BinderProxyCountingService.class.getSimpleName();
+
+    private IBinderProxyCountingService.Stub mBinder = new IBinderProxyCountingService.Stub() {
+
+        final RemoteCallbackList<ITestRemoteCallback> mTestCallbacks = new RemoteCallbackList<>();
+
+        @Override
+        public void registerCallback(ITestRemoteCallback callback) {
+            synchronized (this) {
+                mTestCallbacks.register(callback);
+            }
+        }
+
+        @Override
+        public void unregisterCallback(ITestRemoteCallback callback) {
+            synchronized (this) {
+                mTestCallbacks.unregister(callback);
+            }
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java
new file mode 100644
index 0000000..6bed2a2
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.binderproxycountingtestservice;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.frameworks.coretests.aidl.IBpcCallbackObserver;
+import com.android.frameworks.coretests.aidl.IBpcTestServiceCmdService;
+import com.android.internal.os.BinderInternal;
+
+public class BpcTestServiceCmdService extends Service {
+    private static final String TAG = BpcTestServiceCmdService.class.getSimpleName();
+
+    //ServiceThread mHandlerThread;
+    Handler mHandler;
+    HandlerThread mHandlerThread;
+
+    private IBpcTestServiceCmdService.Stub mBinder = new IBpcTestServiceCmdService.Stub() {
+        IBpcCallbackObserver mCallbackObserver;
+
+        @Override
+        public void forceGc() {
+            int gcCount = Integer.parseInt(Debug.getRuntimeStat("art.gc.gc-count"));
+            int i = 20;
+            while (gcCount == Integer.parseInt(Debug.getRuntimeStat("art.gc.gc-count")) && i > 0) {
+                System.gc();
+                System.runFinalization();
+                i--;
+            }
+        }
+
+        @Override
+        public int getBinderProxyCount(int uid) {
+            return BinderInternal.nGetBinderProxyCount(uid);
+        }
+
+        @Override
+        public void setBinderProxyWatermarks(int high, int low) {
+            BinderInternal.nSetBinderProxyCountWatermarks(high, low);
+        }
+
+        @Override
+        public void enableBinderProxyLimit(boolean enable) {
+            BinderInternal.nSetBinderProxyCountEnabled(enable);
+        }
+
+        @Override
+        public void setBinderProxyCountCallback(IBpcCallbackObserver observer) {
+            if (observer != null) {
+                BinderInternal.setBinderProxyCountCallback(
+                        new BinderInternal.BinderProxyLimitListener() {
+                            @Override
+                            public void onLimitReached(int uid) {
+                                try {
+                                    synchronized (observer) {
+                                        observer.onCallback(uid);
+                                    }
+                                } catch (Exception e) {
+                                    Log.e(TAG, e.toString());
+                                }
+                            }
+                        }, mHandler);
+            } else {
+                BinderInternal.clearBinderProxyCountCallback();
+            }
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    @Override
+    public void onCreate()
+    {
+        mHandlerThread = new HandlerThread("BinderProxyCountingServiceThread");
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/Android.mk b/core/tests/coretests/aidl/Android.mk
new file mode 100644
index 0000000..86e36b6
--- /dev/null
+++ b/core/tests/coretests/aidl/Android.mk
@@ -0,0 +1,22 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-subdir-Iaidl-files)
+LOCAL_MODULE := coretests-aidl
+include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBinderProxyCountingService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBinderProxyCountingService.aidl
new file mode 100644
index 0000000..a69b0c5
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBinderProxyCountingService.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.aidl;
+import com.android.frameworks.coretests.aidl.ITestRemoteCallback;
+
+interface IBinderProxyCountingService {
+    void registerCallback(in ITestRemoteCallback callback);
+    void unregisterCallback(in ITestRemoteCallback callback);
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl
new file mode 100644
index 0000000..c4ebd56
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.aidl;
+
+interface IBpcCallbackObserver {
+    void onCallback(int uid);
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestAppCmdService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestAppCmdService.aidl
new file mode 100644
index 0000000..86a0aa0f
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestAppCmdService.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.aidl;
+
+interface IBpcTestAppCmdService {
+   void createSystemBinders(int count);
+   void releaseSystemBinders(int count);
+
+   void createTestBinders(int count);
+   void releaseTestBinders(int count);
+
+   void releaseAllBinders();
+
+   String bindToTestService();
+   void unbindFromTestService();
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl
new file mode 100644
index 0000000..abdab41
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.aidl;
+import com.android.frameworks.coretests.aidl.IBpcCallbackObserver;
+
+interface IBpcTestServiceCmdService {
+   void forceGc();
+   int getBinderProxyCount(int uid);
+   void setBinderProxyWatermarks(int high, int low);
+   void enableBinderProxyLimit(boolean enable);
+   void setBinderProxyCountCallback(IBpcCallbackObserver observer);
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestRemoteCallback.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestRemoteCallback.aidl
new file mode 100644
index 0000000..36bdb6c
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestRemoteCallback.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.aidl;
+
+interface ITestRemoteCallback {
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
new file mode 100644
index 0000000..6cdb35ab
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import com.android.frameworks.coretests.aidl.IBpcCallbackObserver;
+import com.android.frameworks.coretests.aidl.IBpcTestAppCmdService;
+import com.android.frameworks.coretests.aidl.IBpcTestServiceCmdService;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Tests for verifying the Binder Proxy Counting and Limiting.
+ *
+ * To manually build and install relevant test apps
+ *
+ * Build:
+ * mmma frameworks/base/core/tests/coretests/BinderProxyCountingTestApp
+ * mmma frameworks/base/core/tests/coretests/BinderProxyCountingTestService
+ * Install:
+ * adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/BinderProxyCountingTestApp/BinderProxyCountingTestApp.apk
+ * adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/BinderProxyCountingTestService/BinderProxyCountingTestService.apk
+ *
+ * To run the tests, use
+ *
+ * Build: m FrameworksCoreTests
+ * Install: adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
+ * Run: adb shell am instrument -e class android.os.BinderProxyCountingTest -w \
+ * com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
+ *
+ * or
+ *
+ * bit FrameworksCoreTests:android.os.BinderProxyCountingTest
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BinderProxyCountingTest {
+    private static final String TAG = BinderProxyCountingTest.class.getSimpleName();
+
+    private static final String TEST_APP_PKG =
+            "com.android.frameworks.coretests.binderproxycountingtestapp";
+    private static final String TEST_APP_CMD_SERVICE = TEST_APP_PKG + ".BpcTestAppCmdService";
+    private static final String TEST_SERVICE_PKG =
+            "com.android.frameworks.coretests.binderproxycountingtestservice";
+    private static final String TEST_SERVICE_CMD_SERVICE =
+            TEST_SERVICE_PKG + ".BpcTestServiceCmdService";
+
+    private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
+    private static final int TOO_MANY_BINDERS_TIMEOUT_SEC = 2;
+
+    // Keep in sync with sBinderProxyCountLimit in BpBinder.cpp
+    private static final int BINDER_PROXY_LIMIT = 2500;
+
+    private static Context sContext;
+    private static UiDevice sUiDevice;
+
+    private static ServiceConnection sTestAppConnection;
+    private static ServiceConnection sTestServiceConnection;
+    private static IBpcTestAppCmdService sBpcTestAppCmdService;
+    private static IBpcTestServiceCmdService sBpcTestServiceCmdService;
+    private static final Intent sTestAppIntent = new Intent()
+            .setComponent(new ComponentName(TEST_APP_PKG, TEST_APP_CMD_SERVICE));
+    private static final Intent sTestServiceIntent = new Intent()
+            .setComponent(new ComponentName(TEST_SERVICE_PKG, TEST_SERVICE_CMD_SERVICE));
+    private static final Consumer<IBinder> sTestAppConsumer = (service) -> {
+        sBpcTestAppCmdService = IBpcTestAppCmdService.Stub.asInterface(service);
+    };
+    private static final Consumer<IBinder> sTestServiceConsumer = (service) -> {
+        sBpcTestServiceCmdService = IBpcTestServiceCmdService.Stub.asInterface(service);
+    };
+    private static int sTestPkgUid;
+
+    /**
+     * Setup any common data for the upcoming tests.
+     */
+    @BeforeClass
+    public static void setUpOnce() throws Exception {
+        sContext = InstrumentationRegistry.getContext();
+        sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_APP_PKG, 0);
+        ((ActivityManager) sContext.getSystemService(Context.ACTIVITY_SERVICE)).killUid(sTestPkgUid,
+                "Wiping Test Package");
+
+        sUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+    }
+
+    private ServiceConnection bindService(final Consumer<IBinder> consumer, Intent intent)
+            throws Exception {
+        final CountDownLatch bindLatch = new CountDownLatch(1);
+        ServiceConnection connection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                Log.i(TAG, "Service connected");
+                consumer.accept(service);
+                bindLatch.countDown();
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                Log.i(TAG, "Service disconnected");
+            }
+        };
+        sContext.bindService(intent, connection,
+                Context.BIND_AUTO_CREATE
+                        | Context.BIND_ALLOW_OOM_MANAGEMENT
+                        | Context.BIND_NOT_FOREGROUND);
+        if (!bindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+            fail("Timed out waiting for the service to bind in " + sTestPkgUid);
+        }
+        return connection;
+    }
+
+
+    private void unbindService(ServiceConnection service) {
+        if (service != null) {
+            sContext.unbindService(service);
+        }
+    }
+
+    private void bindTestAppToTestService() throws Exception {
+        if (sBpcTestAppCmdService != null) {
+            String errorMessage = sBpcTestAppCmdService.bindToTestService();
+            if (errorMessage != null) {
+                fail(errorMessage);
+            }
+        }
+    }
+
+    private void unbindTestAppFromTestService() throws Exception {
+        if (sBpcTestAppCmdService != null) {
+            sBpcTestAppCmdService.unbindFromTestService();
+        }
+    }
+
+    private CountDownLatch createBinderLimitLatch() throws RemoteException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        sBpcTestServiceCmdService.setBinderProxyCountCallback(
+                new IBpcCallbackObserver.Stub() {
+                    @Override
+                    public void onCallback(int uid) {
+                        if (uid == sTestPkgUid) {
+                            latch.countDown();
+                        }
+                    }
+                });
+        return latch;
+    }
+
+    /**
+     * Get the Binder Proxy count held by SYSTEM for a given uid
+     */
+    private int getSystemBinderCount(int uid) throws Exception {
+        return Integer.parseInt(sUiDevice.executeShellCommand(
+                "dumpsys activity binder-proxies " + uid).trim());
+    }
+
+    @Test
+    public void testBinderProxyCount() throws Exception {
+        // Arbitrary list of Binder create and release
+        // Should cumulatively equal 0 and must never add up past the binder limit at any point
+        int[] testValues = {223, -103, -13, 25, 90, -222};
+        try {
+            sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+            // Get the baseline of binders naturally held by the test Package
+            int expectedBinderCount = getSystemBinderCount(sTestPkgUid);
+
+            for (int testValue : testValues) {
+                if (testValue > 0) {
+                    sBpcTestAppCmdService.createSystemBinders(testValue);
+                } else {
+                    sBpcTestAppCmdService.releaseSystemBinders(-testValue);
+                }
+                expectedBinderCount += testValue;
+                int currentBinderCount = getSystemBinderCount(sTestPkgUid);
+                assertEquals("Current Binder Count (" + currentBinderCount
+                        + ") does not equal expected Binder Count (" + expectedBinderCount
+                        + ")", expectedBinderCount, currentBinderCount);
+            }
+        } finally {
+            unbindService(sTestAppConnection);
+        }
+    }
+
+    @Test
+    public void testBinderProxyLimitBoundary() throws Exception {
+        final int binderProxyLimit = 2000;
+        final int rearmThreshold = 1800;
+        try {
+            sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+            sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent);
+            bindTestAppToTestService();
+            sBpcTestServiceCmdService.enableBinderProxyLimit(true);
+
+            sBpcTestServiceCmdService.forceGc();
+            // Get the baseline of binders naturally held by the test Package
+            int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+
+            final CountDownLatch binderLimitLatch = createBinderLimitLatch();
+            sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold);
+
+            // Create Binder Proxies up to the limit
+            sBpcTestAppCmdService.createTestBinders(binderProxyLimit - baseBinderCount);
+            if (binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
+                        + " when proxy limit should not have been reached");
+            }
+
+            // Create one more Binder to cross the limit
+            sBpcTestAppCmdService.createTestBinders(1);
+            if (!binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
+            }
+
+            sBpcTestAppCmdService.releaseAllBinders();
+        } finally {
+            unbindTestAppFromTestService();
+            unbindService(sTestAppConnection);
+            unbindService(sTestServiceConnection);
+        }
+    }
+
+    @Test
+    public void testSetBinderProxyLimit() throws Exception {
+        int[] testLimits = {1000, 222, 800};
+        try {
+            sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+            sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent);
+            bindTestAppToTestService();
+            sBpcTestServiceCmdService.enableBinderProxyLimit(true);
+
+            sBpcTestServiceCmdService.forceGc();
+            int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+            for (int testLimit : testLimits) {
+                final CountDownLatch binderLimitLatch = createBinderLimitLatch();
+                // Change the BinderProxyLimit
+                sBpcTestServiceCmdService.setBinderProxyWatermarks(testLimit, baseBinderCount + 10);
+                // Exceed the new Binder Proxy Limit
+                sBpcTestAppCmdService.createTestBinders(testLimit + 1);
+                if (!binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                    fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
+                }
+
+                sBpcTestAppCmdService.releaseTestBinders(testLimit + 1);
+                sBpcTestServiceCmdService.forceGc();
+            }
+        } finally {
+            unbindTestAppFromTestService();
+            unbindService(sTestAppConnection);
+            unbindService(sTestServiceConnection);
+        }
+    }
+
+    @Test
+    public void testRearmCallbackThreshold() throws Exception {
+        final int binderProxyLimit = 2000;
+        final int exceedBinderProxyLimit = binderProxyLimit + 10;
+        final int rearmThreshold = 1800;
+        try {
+            sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+            sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent);
+            bindTestAppToTestService();
+            sBpcTestServiceCmdService.enableBinderProxyLimit(true);
+
+            sBpcTestServiceCmdService.forceGc();
+            final CountDownLatch firstBinderLimitLatch = createBinderLimitLatch();
+            sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold);
+            // Exceed the Binder Proxy Limit
+            sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit);
+            if (!firstBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
+            }
+
+            sBpcTestServiceCmdService.forceGc();
+            int currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+            // Drop to the threshold, this should not rearm the callback
+            sBpcTestAppCmdService.releaseTestBinders(currentBinderCount - rearmThreshold);
+
+            sBpcTestServiceCmdService.forceGc();
+            currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+
+            final CountDownLatch secondBinderLimitLatch = createBinderLimitLatch();
+            // Exceed the Binder Proxy limit which should not cause a callback since there has
+            // been no rearm
+            sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - currentBinderCount);
+            if (secondBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
+                        + " when the callback has not been rearmed yet");
+            }
+
+            sBpcTestServiceCmdService.forceGc();
+            currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+            // Drop below the rearmThreshold to rearm the BinderProxyLimitCallback
+            sBpcTestAppCmdService.releaseTestBinders(currentBinderCount - rearmThreshold + 1);
+
+            sBpcTestServiceCmdService.forceGc();
+            currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+            // Exceed the Binder Proxy limit for the last time
+            sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - currentBinderCount);
+
+            if (!secondBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
+            }
+            sBpcTestAppCmdService.releaseTestBinders(currentBinderCount);
+        } finally {
+            unbindTestAppFromTestService();
+            unbindService(sTestAppConnection);
+            unbindService(sTestServiceConnection);
+        }
+    }
+
+    @Test
+    public void testKillBadBehavingApp() throws Exception {
+        final CountDownLatch binderDeathLatch = new CountDownLatch(1);
+        final int exceedBinderProxyLimit = BINDER_PROXY_LIMIT + 1;
+
+        try {
+            sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+            sBpcTestAppCmdService.asBinder().linkToDeath(new IBinder.DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    Log.v(TAG, "BpcTestAppCmdService died!");
+                    binderDeathLatch.countDown();
+                }
+            }, 0);
+            try {
+                // Exceed the Binder Proxy Limit emulating a bad behaving app
+                sBpcTestAppCmdService.createSystemBinders(exceedBinderProxyLimit);
+            } catch (DeadObjectException doe) {
+                // We are expecting the service to get killed mid call, so a DeadObjectException
+                // is not unexpected
+            }
+
+            if (!binderDeathLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                sBpcTestAppCmdService.releaseSystemBinders(exceedBinderProxyLimit);
+                fail("Timed out waiting for uid " + sTestPkgUid + " to die.");
+            }
+
+        } finally {
+            unbindService(sTestAppConnection);
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java
new file mode 100644
index 0000000..f8d6884
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextLineTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.filters.Suppress;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextLineTest {
+    private boolean stretchesToFullWidth(CharSequence line) {
+        final TextPaint paint = new TextPaint();
+        final TextLine tl = TextLine.obtain();
+        tl.set(paint, line, 0, line.length(), Layout.DIR_LEFT_TO_RIGHT,
+                Layout.DIRS_ALL_LEFT_TO_RIGHT, false /* hasTabs */, null /* tabStops */);
+        final float originalWidth = tl.metrics(null);
+        final float expandedWidth = 2 * originalWidth;
+
+        tl.justify(expandedWidth);
+        final float newWidth = tl.metrics(null);
+        TextLine.recycle(tl);
+        return Math.abs(newWidth - expandedWidth) < 0.5;
+    }
+
+    @Test
+    public void testJustify_spaces() {
+        // There are no spaces to stretch.
+        assertFalse(stretchesToFullWidth("text"));
+
+        assertTrue(stretchesToFullWidth("one space"));
+        assertTrue(stretchesToFullWidth("exactly two spaces"));
+        assertTrue(stretchesToFullWidth("up to three spaces"));
+    }
+
+    // NBSP should also stretch when it's not used as a base for a combining mark. This doesn't work
+    // yet (b/68204709).
+    @Suppress
+    public void disabledTestJustify_NBSP() {
+        final char nbsp = '\u00A0';
+        assertTrue(stretchesToFullWidth("non-breaking" + nbsp + "space"));
+        assertTrue(stretchesToFullWidth("mix" + nbsp + "and match"));
+
+        final char combining_acute = '\u0301';
+        assertFalse(stretchesToFullWidth("combining" + nbsp + combining_acute + "acute"));
+    }
+}
diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk
index 0e6b31e..bec22c9 100644
--- a/legacy-test/Android.mk
+++ b/legacy-test/Android.mk
@@ -93,6 +93,8 @@
 LOCAL_MODULE := legacy.test.stubs
 
 LOCAL_SOURCE_FILES_ALL_GENERATED := true
+LOCAL_SDK_VERSION := current
+LOCAL_ADDITIONAL_DEPENDENCIES := $(legacy_test_api_gen_stamp)
 
 # Make sure to run droiddoc first to generate the stub source files.
 LOCAL_ADDITIONAL_DEPENDENCIES := $(legacy_test_api_gen_stamp)
@@ -162,3 +164,5 @@
 
 include $(BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY)
 endif  # HOST_OS == linux
+
+legacy_test_api_gen_stamp :=
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 5fc5cb2..6c606f7 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -105,8 +105,8 @@
 }
 
 void RenderState::onVkContextDestroyed() {
-    mLayerPool->clear();
     std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerDestroyedVkContext);
+    destroyLayersInUpdater();
     GpuMemoryTracker::onGpuContextDestroyed();
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
index 8869e8d..ddd9910 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -185,10 +185,10 @@
             }
             view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
             final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
-            if (item.iconDrawable != null) {
-                iv.setImageDrawable(item.iconDrawable.getDrawable(iv.getContext()));
+            if (item.icon != null) {
+                iv.setImageDrawable(item.icon.getDrawable(iv.getContext()));
             } else {
-                iv.setImageResource(item.icon);
+                iv.setImageResource(item.iconResId);
             }
             iv.getOverlay().clear();
             if (item.overlay != null) {
@@ -258,8 +258,8 @@
     }
 
     public static class Item {
-        public int icon;
-        public QSTile.Icon iconDrawable;
+        public int iconResId;
+        public QSTile.Icon icon;
         public Drawable overlay;
         public CharSequence line1;
         public CharSequence line2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index bc3ccb4..1aecdce 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -134,7 +134,7 @@
                 if (lastDevice != null) {
                     int batteryLevel = lastDevice.getBatteryLevel();
                     if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                        state.icon = new BluetoothBatteryDrawable(batteryLevel,
+                        state.icon = new BluetoothBatteryTileIcon(batteryLevel,
                                 mContext.getResources().getFraction(
                                         R.fraction.bt_battery_scale_fraction, 1, 1));
                     }
@@ -212,15 +212,11 @@
         return new BluetoothDetailAdapter();
     }
 
-    private class BluetoothBatteryDrawable extends Icon {
+    private class BluetoothBatteryTileIcon extends Icon {
         private int mLevel;
         private float mIconScale;
 
-        BluetoothBatteryDrawable(int level) {
-            this(level, 1 /* iconScale */);
-        }
-
-        BluetoothBatteryDrawable(int level, float iconScale) {
+        BluetoothBatteryTileIcon(int level, float iconScale) {
             mLevel = level;
             mIconScale = iconScale;
         }
@@ -302,15 +298,16 @@
                 for (CachedBluetoothDevice device : devices) {
                     if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
                     final Item item = new Item();
-                    item.icon = R.drawable.ic_qs_bluetooth_on;
+                    item.iconResId = R.drawable.ic_qs_bluetooth_on;
                     item.line1 = device.getName();
                     item.tag = device;
                     int state = device.getMaxConnectionState();
                     if (state == BluetoothProfile.STATE_CONNECTED) {
-                        item.icon = R.drawable.ic_qs_bluetooth_connected;
+                        item.iconResId = R.drawable.ic_qs_bluetooth_connected;
                         int batteryLevel = device.getBatteryLevel();
                         if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                            item.iconDrawable = new BluetoothBatteryDrawable(batteryLevel);
+                            item.icon = new BluetoothBatteryTileIcon(batteryLevel,
+                                    1 /* iconScale */);
                             item.line2 = mContext.getString(
                                     R.string.quick_settings_connected_battery_level,
                                     Utils.formatPercentage(batteryLevel));
@@ -321,7 +318,7 @@
                         items.add(connectedDevices, item);
                         connectedDevices++;
                     } else if (state == BluetoothProfile.STATE_CONNECTING) {
-                        item.icon = R.drawable.ic_qs_bluetooth_connecting;
+                        item.iconResId = R.drawable.ic_qs_bluetooth_connecting;
                         item.line2 = mContext.getString(R.string.quick_settings_connecting);
                         items.add(connectedDevices, item);
                     } else {
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 fb396b9..678aa71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -19,26 +19,17 @@
 import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
 
 import android.app.Dialog;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.MediaRouter;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.quicksettings.Tile;
 import android.util.Log;
-import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
-import android.view.View.OnClickListener;
 import android.view.ViewGroup;
-import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.widget.Button;
 
-import com.android.internal.app.MediaRouteChooserDialog;
-import com.android.internal.app.MediaRouteControllerDialog;
 import com.android.internal.app.MediaRouteDialogPresenter;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -280,7 +271,7 @@
                 for (CastDevice device : devices) {
                     if (device.state == CastDevice.STATE_CONNECTED) {
                         final Item item = new Item();
-                        item.icon = R.drawable.ic_qs_cast_on;
+                        item.iconResId = R.drawable.ic_qs_cast_on;
                         item.line1 = getDeviceName(device);
                         item.line2 = mContext.getString(R.string.quick_settings_connected);
                         item.tag = device;
@@ -300,7 +291,7 @@
                         final CastDevice device = mVisibleOrder.get(id);
                         if (!devices.contains(device)) continue;
                         final Item item = new Item();
-                        item.icon = R.drawable.ic_qs_cast_off;
+                        item.iconResId = R.drawable.ic_qs_cast_off;
                         item.line1 = getDeviceName(device);
                         if (device.state == CastDevice.STATE_CONNECTING) {
                             item.line2 = mContext.getString(R.string.quick_settings_connecting);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 2370273..977a725 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -402,7 +402,7 @@
                     final AccessPoint ap = mAccessPoints[i];
                     final Item item = new Item();
                     item.tag = ap;
-                    item.icon = mWifiController.getIcon(ap);
+                    item.iconResId = mWifiController.getIcon(ap);
                     item.line1 = ap.getSsid();
                     item.line2 = ap.isActive() ? ap.getSummary() : null;
                     item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 8e88359..5dd3620 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4562,6 +4562,11 @@
     // OS: O MR
     NOTIFICATION_SNOOZE_OPTIONS = 1142;
 
+    // OPEN: Settings > Display > Colors
+    // CATEGORY: SETTINGS
+    // OS: O MR
+    COLOR_MODE_SETTINGS = 1143;
+
     // ---- End O-MR1 Constants, all O-MR1 constants go above this line ----
 
     // OPEN: Settings > Network & Internet > Mobile network
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 76e7782..6c15438 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -107,8 +107,6 @@
 import com.android.server.WidgetBackupProvider;
 import com.android.server.policy.IconUtilities;
 
-import libcore.io.IoUtils;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -174,21 +172,27 @@
                 Slog.i(TAG, "Received broadcast: " + action + " on user " + userId);
             }
 
-            if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
-                onConfigurationChanged();
-            } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
-                    || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
-                synchronized (mLock) {
-                    reloadWidgetsMaskedState(userId);
-                }
-            } else if (Intent.ACTION_PACKAGES_SUSPENDED.equals(action)) {
-                String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                updateWidgetPackageSuspensionMaskedState(packages, true, getSendingUserId());
-            } else if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(action)) {
-                String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                updateWidgetPackageSuspensionMaskedState(packages, false, getSendingUserId());
-            } else {
-                onPackageBroadcastReceived(intent, userId);
+            switch (action) {
+                case Intent.ACTION_CONFIGURATION_CHANGED:
+                    onConfigurationChanged();
+                    break;
+                case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
+                case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
+                    synchronized (mLock) {
+                        reloadWidgetsMaskedState(userId);
+                    }
+                    break;
+                case Intent.ACTION_PACKAGES_SUSPENDED:
+                    onPackageBroadcastReceived(intent, getSendingUserId());
+                    updateWidgetPackageSuspensionMaskedState(intent, true, getSendingUserId());
+                    break;
+                case Intent.ACTION_PACKAGES_UNSUSPENDED:
+                    onPackageBroadcastReceived(intent, getSendingUserId());
+                    updateWidgetPackageSuspensionMaskedState(intent, false, getSendingUserId());
+                    break;
+                default:
+                    onPackageBroadcastReceived(intent, getSendingUserId());
+                    break;
             }
         }
     };
@@ -378,25 +382,32 @@
         boolean changed = false;
         boolean componentsModified = false;
 
-        String pkgList[] = null;
-        if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
-            pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            added = true;
-        } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
-            pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            added = false;
-        } else {
-            Uri uri = intent.getData();
-            if (uri == null) {
-                return;
+        final String pkgList[];
+        switch (action) {
+            case Intent.ACTION_PACKAGES_SUSPENDED:
+            case Intent.ACTION_PACKAGES_UNSUSPENDED:
+                pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                changed = true;
+                break;
+            case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+                added = true;
+                // Follow through
+            case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+                pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                break;
+            default: {
+                Uri uri = intent.getData();
+                if (uri == null) {
+                    return;
+                }
+                String pkgName = uri.getSchemeSpecificPart();
+                if (pkgName == null) {
+                    return;
+                }
+                pkgList = new String[] { pkgName };
+                added = Intent.ACTION_PACKAGE_ADDED.equals(action);
+                changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
             }
-            String pkgName = uri.getSchemeSpecificPart();
-            if (pkgName == null) {
-                return;
-            }
-            pkgList = new String[] { pkgName };
-            added = Intent.ACTION_PACKAGE_ADDED.equals(action);
-            changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
         }
         if (pkgList == null || pkgList.length == 0) {
             return;
@@ -516,12 +527,13 @@
     /**
      * Incrementally update the masked state due to package suspension state.
      */
-    private void updateWidgetPackageSuspensionMaskedState(String[] packagesArray, boolean suspended,
+    private void updateWidgetPackageSuspensionMaskedState(Intent intent, boolean suspended,
             int profileId) {
+        String[] packagesArray = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
         if (packagesArray == null) {
             return;
         }
-        Set<String> packages = new ArraySet<String>(Arrays.asList(packagesArray));
+        Set<String> packages = new ArraySet<>(Arrays.asList(packagesArray));
         synchronized (mLock) {
             final int N = mProviders.size();
             for (int i = 0; i < N; i++) {
@@ -2630,11 +2642,9 @@
 
             // No file written for this user - nothing to do.
             AtomicFile file = getSavedStateFile(profileId);
-            try {
-                FileInputStream stream = file.openRead();
+            try (FileInputStream stream = file.openRead()) {
                 version = readProfileStateFromFileLocked(stream, profileId, loadedWidgets);
-                IoUtils.closeQuietly(stream);
-            } catch (FileNotFoundException e) {
+            } catch (IOException e) {
                 Slog.w(TAG, "Failed to read state: " + e);
             }
         }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 4c08f62..3904fc9 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -63,6 +63,7 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseLongArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
@@ -190,7 +191,8 @@
 
     /**
      * For each uid, this is the last time we dispatched an "allow while idle" alarm,
-     * used to determine the earliest we can dispatch the next such alarm.
+     * used to determine the earliest we can dispatch the next such alarm. Times are in the
+     * 'elapsed' timebase.
      */
     final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray();
 
@@ -355,6 +357,22 @@
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw);
             pw.println();
         }
+
+        void dumpProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(ConstantsProto.MIN_FUTURITY_DURATION_MS, MIN_FUTURITY);
+            proto.write(ConstantsProto.MIN_INTERVAL_DURATION_MS, MIN_INTERVAL);
+            proto.write(ConstantsProto.LISTENER_TIMEOUT_DURATION_MS, LISTENER_TIMEOUT);
+            proto.write(ConstantsProto.ALLOW_WHILE_IDLE_SHORT_DURATION_MS,
+                    ALLOW_WHILE_IDLE_SHORT_TIME);
+            proto.write(ConstantsProto.ALLOW_WHILE_IDLE_LONG_DURATION_MS,
+                    ALLOW_WHILE_IDLE_LONG_TIME);
+            proto.write(ConstantsProto.ALLOW_WHILE_IDLE_WHITELIST_DURATION_MS,
+                    ALLOW_WHILE_IDLE_WHITELIST_DURATION);
+
+            proto.end(token);
+        }
     }
 
     final Constants mConstants;
@@ -632,6 +650,20 @@
             b.append('}');
             return b.toString();
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId, long nowElapsed,
+                long nowRTC) {
+            final long token = proto.start(fieldId);
+
+            proto.write(BatchProto.START_REALTIME, start);
+            proto.write(BatchProto.END_REALTIME, end);
+            proto.write(BatchProto.FLAGS, flags);
+            for (Alarm a : alarms) {
+                a.writeToProto(proto, BatchProto.ALARMS, nowElapsed, nowRTC);
+            }
+
+            proto.end(token);
+        }
     }
 
     static class BatchTimeOrder implements Comparator<Batch> {
@@ -1007,6 +1039,29 @@
                     + ", alarmType=" + mAlarmType
                     + "}";
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(InFlightProto.UID, mUid);
+            proto.write(InFlightProto.TAG, mTag);
+            proto.write(InFlightProto.WHEN_ELAPSED_MS, mWhenElapsed);
+            proto.write(InFlightProto.ALARM_TYPE, mAlarmType);
+            if (mPendingIntent != null) {
+                mPendingIntent.writeToProto(proto, InFlightProto.PENDING_INTENT);
+            }
+            if (mBroadcastStats != null) {
+                mBroadcastStats.writeToProto(proto, InFlightProto.BROADCAST_STATS);
+            }
+            if (mFilterStats != null) {
+                mFilterStats.writeToProto(proto, InFlightProto.FILTER_STATS);
+            }
+            if (mWorkSource != null) {
+                mWorkSource.writeToProto(proto, InFlightProto.WORK_SOURCE);
+            }
+
+            proto.end(token);
+        }
     }
 
     static final class FilterStats {
@@ -1037,6 +1092,20 @@
                     + ", nesting=" + nesting
                     + "}";
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(FilterStatsProto.TAG, mTag);
+            proto.write(FilterStatsProto.LAST_FLIGHT_TIME_REALTIME, lastTime);
+            proto.write(FilterStatsProto.TOTAL_FLIGHT_DURATION_MS, aggregateTime);
+            proto.write(FilterStatsProto.COUNT, count);
+            proto.write(FilterStatsProto.WAKEUP_COUNT, numWakeup);
+            proto.write(FilterStatsProto.START_TIME_REALTIME, startTime);
+            proto.write(FilterStatsProto.NESTING, nesting);
+
+            proto.end(token);
+        }
     }
 
     static final class BroadcastStats {
@@ -1067,6 +1136,20 @@
                     + ", nesting=" + nesting
                     + "}";
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(BroadcastStatsProto.UID, mUid);
+            proto.write(BroadcastStatsProto.PACKAGE_NAME, mPackageName);
+            proto.write(BroadcastStatsProto.TOTAL_FLIGHT_DURATION_MS, aggregateTime);
+            proto.write(BroadcastStatsProto.COUNT, count);
+            proto.write(BroadcastStatsProto.WAKEUP_COUNT, numWakeup);
+            proto.write(BroadcastStatsProto.START_TIME_REALTIME, startTime);
+            proto.write(BroadcastStatsProto.NESTING, nesting);
+
+            proto.end(token);
+        }
     }
 
     final SparseArray<ArrayMap<String, BroadcastStats>> mBroadcastStats
@@ -1128,14 +1211,14 @@
                 | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
         mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent,
                 Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL);
-        
+
         // now that we have initied the driver schedule the alarm
         mClockReceiver = new ClockReceiver();
         mClockReceiver.scheduleTimeTickEvent();
         mClockReceiver.scheduleDateChangedEvent();
         mInteractiveStateReceiver = new InteractiveStateReceiver();
         mUninstallReceiver = new UninstallReceiver();
-        
+
         if (mNativeData != 0) {
             AlarmThread waitThread = new AlarmThread();
             waitThread.start();
@@ -1568,7 +1651,12 @@
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
-            dumpImpl(pw);
+
+            if (args.length > 0 && "--proto".equals(args[0])) {
+                dumpProto(fd);
+            } else {
+                dumpImpl(pw);
+            }
         }
     };
 
@@ -1681,7 +1769,7 @@
                 pw.print("      Idling until: ");
                 if (mPendingIdleUntil != null) {
                     pw.println(mPendingIdleUntil);
-                    mPendingIdleUntil.dump(pw, "        ", nowRTC, nowELAPSED, sdf);
+                    mPendingIdleUntil.dump(pw, "        ", nowELAPSED, nowRTC, sdf);
                 } else {
                     pw.println("null");
                 }
@@ -1691,7 +1779,7 @@
             if (mNextWakeFromIdle != null) {
                 pw.println();
                 pw.print("  Next wake from idle: "); pw.println(mNextWakeFromIdle);
-                mNextWakeFromIdle.dump(pw, "    ", nowRTC, nowELAPSED, sdf);
+                mNextWakeFromIdle.dump(pw, "    ", nowELAPSED, nowRTC, sdf);
             }
 
             pw.println();
@@ -1760,6 +1848,7 @@
                 }
             };
             int len = 0;
+            // Get the top 10 FilterStats, ordered by aggregateTime.
             for (int iu=0; iu<mBroadcastStats.size(); iu++) {
                 ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu);
                 for (int ip=0; ip<uidStats.size(); ip++) {
@@ -1886,6 +1975,244 @@
         }
     }
 
+    void dumpProto(FileDescriptor fd) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+        synchronized (mLock) {
+            final long nowRTC = System.currentTimeMillis();
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            proto.write(AlarmManagerServiceProto.CURRENT_TIME, nowRTC);
+            proto.write(AlarmManagerServiceProto.ELAPSED_REALTIME, nowElapsed);
+            proto.write(AlarmManagerServiceProto.LAST_TIME_CHANGE_CLOCK_TIME,
+                    mLastTimeChangeClockTime);
+            proto.write(AlarmManagerServiceProto.LAST_TIME_CHANGE_REALTIME,
+                    mLastTimeChangeRealtime);
+
+            mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS);
+
+            final int foregroundUidsSize = mForegroundUids.size();
+            for (int i = 0; i < foregroundUidsSize; i++) {
+                if (mForegroundUids.valueAt(i)) {
+                    proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i));
+                }
+            }
+            for (String pkg : mForcedAppStandbyPackages) {
+                proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg);
+            }
+
+            proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive);
+            if (!mInteractive) {
+                // Durations
+                proto.write(AlarmManagerServiceProto.TIME_SINCE_NON_INTERACTIVE_MS,
+                        nowElapsed - mNonInteractiveStartTime);
+                proto.write(AlarmManagerServiceProto.MAX_WAKEUP_DELAY_MS,
+                        currentNonWakeupFuzzLocked(nowElapsed));
+                proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_DISPATCH_MS,
+                        nowElapsed - mLastAlarmDeliveryTime);
+                proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_NON_WAKEUP_DELIVERY_MS,
+                        nowElapsed - mNextNonWakeupDeliveryTime);
+            }
+
+            proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_NON_WAKEUP_ALARM_MS,
+                    mNextNonWakeup - nowElapsed);
+            proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_WAKEUP_MS,
+                    mNextWakeup - nowElapsed);
+            proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_MS,
+                    nowElapsed - mLastWakeup);
+            proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS,
+                    nowElapsed - mLastWakeupSet);
+            proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged);
+            for (int i : mDeviceIdleUserWhitelist) {
+                proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i);
+            }
+
+            final TreeSet<Integer> users = new TreeSet<>();
+            final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size();
+            for (int i = 0; i < nextAlarmClockForUserSize; i++) {
+                users.add(mNextAlarmClockForUser.keyAt(i));
+            }
+            final int pendingSendNextAlarmClockChangedForUserSize =
+                    mPendingSendNextAlarmClockChangedForUser.size();
+            for (int i = 0; i < pendingSendNextAlarmClockChangedForUserSize; i++) {
+                users.add(mPendingSendNextAlarmClockChangedForUser.keyAt(i));
+            }
+            for (int user : users) {
+                final AlarmManager.AlarmClockInfo next = mNextAlarmClockForUser.get(user);
+                final long time = next != null ? next.getTriggerTime() : 0;
+                final boolean pendingSend = mPendingSendNextAlarmClockChangedForUser.get(user);
+                final long aToken = proto.start(AlarmManagerServiceProto.NEXT_ALARM_CLOCK_METADATA);
+                proto.write(AlarmClockMetadataProto.USER, user);
+                proto.write(AlarmClockMetadataProto.IS_PENDING_SEND, pendingSend);
+                proto.write(AlarmClockMetadataProto.TRIGGER_TIME_MS, time);
+                proto.end(aToken);
+            }
+            for (Batch b : mAlarmBatches) {
+                b.writeToProto(proto, AlarmManagerServiceProto.PENDING_ALARM_BATCHES,
+                        nowElapsed, nowRTC);
+            }
+            for (int i = 0; i < mPendingBackgroundAlarms.size(); i++) {
+                final ArrayList<Alarm> blockedAlarms = mPendingBackgroundAlarms.valueAt(i);
+                if (blockedAlarms != null) {
+                    for (Alarm a : blockedAlarms) {
+                        a.writeToProto(proto,
+                                AlarmManagerServiceProto.PENDING_USER_BLOCKED_BACKGROUND_ALARMS,
+                                nowElapsed, nowRTC);
+                    }
+                }
+            }
+            if (mPendingIdleUntil != null) {
+                mPendingIdleUntil.writeToProto(
+                        proto, AlarmManagerServiceProto.PENDING_IDLE_UNTIL, nowElapsed, nowRTC);
+            }
+            for (Alarm a : mPendingWhileIdleAlarms) {
+                a.writeToProto(proto, AlarmManagerServiceProto.PENDING_WHILE_IDLE_ALARMS,
+                        nowElapsed, nowRTC);
+            }
+            if (mNextWakeFromIdle != null) {
+                mNextWakeFromIdle.writeToProto(proto, AlarmManagerServiceProto.NEXT_WAKE_FROM_IDLE,
+                        nowElapsed, nowRTC);
+            }
+
+            for (Alarm a : mPendingNonWakeupAlarms) {
+                a.writeToProto(proto, AlarmManagerServiceProto.PAST_DUE_NON_WAKEUP_ALARMS,
+                        nowElapsed, nowRTC);
+            }
+
+            proto.write(AlarmManagerServiceProto.DELAYED_ALARM_COUNT, mNumDelayedAlarms);
+            proto.write(AlarmManagerServiceProto.TOTAL_DELAY_TIME_MS, mTotalDelayTime);
+            proto.write(AlarmManagerServiceProto.MAX_DELAY_DURATION_MS, mMaxDelayTime);
+            proto.write(AlarmManagerServiceProto.MAX_NON_INTERACTIVE_DURATION_MS,
+                    mNonInteractiveTime);
+
+            proto.write(AlarmManagerServiceProto.BROADCAST_REF_COUNT, mBroadcastRefCount);
+            proto.write(AlarmManagerServiceProto.PENDING_INTENT_SEND_COUNT, mSendCount);
+            proto.write(AlarmManagerServiceProto.PENDING_INTENT_FINISH_COUNT, mSendFinishCount);
+            proto.write(AlarmManagerServiceProto.LISTENER_SEND_COUNT, mListenerCount);
+            proto.write(AlarmManagerServiceProto.LISTENER_FINISH_COUNT, mListenerFinishCount);
+
+            for (InFlight f : mInFlight) {
+                f.writeToProto(proto, AlarmManagerServiceProto.OUTSTANDING_DELIVERIES);
+            }
+
+            proto.write(AlarmManagerServiceProto.ALLOW_WHILE_IDLE_MIN_DURATION_MS,
+                    mAllowWhileIdleMinTime);
+            for (int i = 0; i < mLastAllowWhileIdleDispatch.size(); ++i) {
+                final long token = proto.start(
+                        AlarmManagerServiceProto.LAST_ALLOW_WHILE_IDLE_DISPATCH_TIMES);
+                proto.write(AlarmManagerServiceProto.LastAllowWhileIdleDispatch.UID,
+                        mLastAllowWhileIdleDispatch.keyAt(i));
+                proto.write(AlarmManagerServiceProto.LastAllowWhileIdleDispatch.TIME_MS,
+                        mLastAllowWhileIdleDispatch.valueAt(i));
+                proto.end(token);
+            }
+
+            mLog.writeToProto(proto, AlarmManagerServiceProto.RECENT_PROBLEMS);
+
+            final FilterStats[] topFilters = new FilterStats[10];
+            final Comparator<FilterStats> comparator = new Comparator<FilterStats>() {
+                @Override
+                public int compare(FilterStats lhs, FilterStats rhs) {
+                    if (lhs.aggregateTime < rhs.aggregateTime) {
+                        return 1;
+                    } else if (lhs.aggregateTime > rhs.aggregateTime) {
+                        return -1;
+                    }
+                    return 0;
+                }
+            };
+            int len = 0;
+            // Get the top 10 FilterStats, ordered by aggregateTime.
+            for (int iu = 0; iu < mBroadcastStats.size(); ++iu) {
+                ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu);
+                for (int ip = 0; ip < uidStats.size(); ++ip) {
+                    BroadcastStats bs = uidStats.valueAt(ip);
+                    for (int is = 0; is < bs.filterStats.size(); ++is) {
+                        FilterStats fs = bs.filterStats.valueAt(is);
+                        int pos = len > 0
+                                ? Arrays.binarySearch(topFilters, 0, len, fs, comparator) : 0;
+                        if (pos < 0) {
+                            pos = -pos - 1;
+                        }
+                        if (pos < topFilters.length) {
+                            int copylen = topFilters.length - pos - 1;
+                            if (copylen > 0) {
+                                System.arraycopy(topFilters, pos, topFilters, pos+1, copylen);
+                            }
+                            topFilters[pos] = fs;
+                            if (len < topFilters.length) {
+                                len++;
+                            }
+                        }
+                    }
+                }
+            }
+            for (int i = 0; i < len; ++i) {
+                final long token = proto.start(AlarmManagerServiceProto.TOP_ALARMS);
+                FilterStats fs = topFilters[i];
+
+                proto.write(AlarmManagerServiceProto.TopAlarm.UID, fs.mBroadcastStats.mUid);
+                proto.write(AlarmManagerServiceProto.TopAlarm.PACKAGE_NAME,
+                        fs.mBroadcastStats.mPackageName);
+                fs.writeToProto(proto, AlarmManagerServiceProto.TopAlarm.FILTER);
+
+                proto.end(token);
+            }
+
+            final ArrayList<FilterStats> tmpFilters = new ArrayList<FilterStats>();
+            for (int iu = 0; iu < mBroadcastStats.size(); ++iu) {
+                ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu);
+                for (int ip = 0; ip < uidStats.size(); ++ip) {
+                    final long token = proto.start(AlarmManagerServiceProto.ALARM_STATS);
+
+                    BroadcastStats bs = uidStats.valueAt(ip);
+                    bs.writeToProto(proto, AlarmManagerServiceProto.AlarmStat.BROADCAST);
+
+                    // uidStats is an ArrayMap, which we can't sort.
+                    tmpFilters.clear();
+                    for (int is = 0; is < bs.filterStats.size(); ++is) {
+                        tmpFilters.add(bs.filterStats.valueAt(is));
+                    }
+                    Collections.sort(tmpFilters, comparator);
+                    for (FilterStats fs : tmpFilters) {
+                        fs.writeToProto(proto, AlarmManagerServiceProto.AlarmStat.FILTERS);
+                    }
+
+                    proto.end(token);
+                }
+            }
+
+            if (RECORD_DEVICE_IDLE_ALARMS) {
+                for (int i = 0; i < mAllowWhileIdleDispatches.size(); i++) {
+                    IdleDispatchEntry ent = mAllowWhileIdleDispatches.get(i);
+                    final long token = proto.start(
+                            AlarmManagerServiceProto.ALLOW_WHILE_IDLE_DISPATCHES);
+
+                    proto.write(IdleDispatchEntryProto.UID, ent.uid);
+                    proto.write(IdleDispatchEntryProto.PKG, ent.pkg);
+                    proto.write(IdleDispatchEntryProto.TAG, ent.tag);
+                    proto.write(IdleDispatchEntryProto.OP, ent.op);
+                    proto.write(IdleDispatchEntryProto.ENTRY_CREATION_REALTIME,
+                            ent.elapsedRealtime);
+                    proto.write(IdleDispatchEntryProto.ARG_REALTIME, ent.argRealtime);
+
+                    proto.end(token);
+                }
+            }
+
+            if (WAKEUP_STATS) {
+                for (WakeupEvent event : mRecentWakeups) {
+                    final long token = proto.start(AlarmManagerServiceProto.RECENT_WAKEUP_HISTORY);
+                    proto.write(WakeupEventProto.UID, event.uid);
+                    proto.write(WakeupEventProto.ACTION, event.action);
+                    proto.write(WakeupEventProto.WHEN, event.when);
+                    proto.end(token);
+                }
+            }
+        }
+
+        proto.flush();
+    }
+
     private void logBatchesLocked(SimpleDateFormat sdf) {
         ByteArrayOutputStream bs = new ByteArrayOutputStream(2048);
         PrintWriter pw = new PrintWriter(bs);
@@ -2328,24 +2655,24 @@
                 alarmSeconds = when / 1000;
                 alarmNanoseconds = (when % 1000) * 1000 * 1000;
             }
-            
+
             set(mNativeData, type, alarmSeconds, alarmNanoseconds);
         } else {
             Message msg = Message.obtain();
             msg.what = ALARM_EVENT;
-            
+
             mHandler.removeMessages(ALARM_EVENT);
             mHandler.sendMessageAtTime(msg, when);
         }
     }
 
     private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list,
-            String prefix, String label, long nowRTC, long nowELAPSED, SimpleDateFormat sdf) {
+            String prefix, String label, long nowELAPSED, long nowRTC, SimpleDateFormat sdf) {
         for (int i=list.size()-1; i>=0; i--) {
             Alarm a = list.get(i);
             pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i);
                     pw.print(": "); pw.println(a);
-            a.dump(pw, prefix + "  ", nowRTC, nowELAPSED, sdf);
+            a.dump(pw, prefix + "  ", nowELAPSED, nowRTC, sdf);
         }
     }
 
@@ -2355,8 +2682,6 @@
         case RTC_WAKEUP : return "RTC_WAKEUP";
         case ELAPSED_REALTIME : return "ELAPSED";
         case ELAPSED_REALTIME_WAKEUP: return "ELAPSED_WAKEUP";
-        default:
-            break;
         }
         return "--unknown--";
     }
@@ -2368,7 +2693,7 @@
             final String label = labelForType(a.type);
             pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i);
                     pw.print(": "); pw.println(a);
-            a.dump(pw, prefix + "  ", nowRTC, nowELAPSED, sdf);
+            a.dump(pw, prefix + "  ", nowELAPSED, nowRTC, sdf);
         }
     }
 
@@ -2533,7 +2858,7 @@
             return 0;
         }
     }
-    
+
     private static class Alarm {
         public final int type;
         public final long origWhen;
@@ -2627,7 +2952,7 @@
             return sb.toString();
         }
 
-        public void dump(PrintWriter pw, String prefix, long nowRTC, long nowELAPSED,
+        public void dump(PrintWriter pw, String prefix, long nowELAPSED, long nowRTC,
                 SimpleDateFormat sdf) {
             final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
             pw.print(prefix); pw.print("tag="); pw.println(statsTag);
@@ -2656,6 +2981,30 @@
                 pw.print(prefix); pw.print("listener="); pw.println(listener.asBinder());
             }
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId, long nowElapsed,
+                long nowRTC) {
+            final long token = proto.start(fieldId);
+
+            proto.write(AlarmProto.TAG, statsTag);
+            proto.write(AlarmProto.TYPE, type);
+            proto.write(AlarmProto.WHEN_ELAPSED_MS, whenElapsed - nowElapsed);
+            proto.write(AlarmProto.WINDOW_LENGTH_MS, windowLength);
+            proto.write(AlarmProto.REPEAT_INTERVAL_MS, repeatInterval);
+            proto.write(AlarmProto.COUNT, count);
+            proto.write(AlarmProto.FLAGS, flags);
+            if (alarmClock != null) {
+                alarmClock.writeToProto(proto, AlarmProto.ALARM_CLOCK);
+            }
+            if (operation != null) {
+                operation.writeToProto(proto, AlarmProto.OPERATION);
+            }
+            if (listener != null) {
+                proto.write(AlarmProto.LISTENER, listener.asBinder().toString());
+            }
+
+            proto.end(token);
+        }
     }
 
     void recordWakeupAlarms(ArrayList<Batch> batches, long nowELAPSED, long nowRTC) {
@@ -2752,7 +3101,7 @@
         {
             super("AlarmManager");
         }
-        
+
         public void run()
         {
             ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
@@ -2918,10 +3267,10 @@
         public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2;
         public static final int LISTENER_TIMEOUT = 3;
         public static final int REPORT_ALARMS_ACTIVE = 4;
-        
+
         public AlarmHandler() {
         }
-        
+
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case ALARM_EVENT: {
@@ -2969,7 +3318,7 @@
             }
         }
     }
-    
+
     class ClockReceiver extends BroadcastReceiver {
         public ClockReceiver() {
             IntentFilter filter = new IntentFilter();
@@ -2977,7 +3326,7 @@
             filter.addAction(Intent.ACTION_DATE_CHANGED);
             getContext().registerReceiver(this, filter);
         }
-        
+
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) {
@@ -2996,7 +3345,7 @@
                 scheduleDateChangedEvent();
             }
         }
-        
+
         public void scheduleTimeTickEvent() {
             final long currentTime = System.currentTimeMillis();
             final long nextTime = 60000 * ((currentTime / 60000) + 1);
@@ -3026,7 +3375,7 @@
                     Process.myUid(), "android");
         }
     }
-    
+
     class InteractiveStateReceiver extends BroadcastReceiver {
         public InteractiveStateReceiver() {
             IntentFilter filter = new IntentFilter();
@@ -3059,7 +3408,7 @@
             sdFilter.addAction(Intent.ACTION_UID_REMOVED);
             getContext().registerReceiver(this, sdFilter);
         }
-        
+
         @Override
         public void onReceive(Context context, Intent intent) {
             synchronized (mLock) {
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 46b671b..e7cf041 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1060,6 +1060,7 @@
         }
         public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
             IHealth service = mHealthServiceWrapper.getLastService();
+            if (service == null) throw new RemoteException("no health service");
             final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED);
             switch(id) {
                 case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
@@ -1101,8 +1102,10 @@
             }
             return outResult.value;
         }
-        public void scheduleUpdate() {
-            Slog.e(TAG, "health: must not call scheduleUpdate on battery properties");
+        public void scheduleUpdate() throws RemoteException {
+            IHealth service = mHealthServiceWrapper.getLastService();
+            if (service == null) throw new RemoteException("no health service");
+            service.update();
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3fd1f4a..a513e14 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -334,6 +334,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.StatsLog;
 import android.util.TimingsTraceLog;
 import android.util.DebugUtils;
 import android.util.DisplayMetrics;
@@ -369,6 +370,7 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.os.BinderInternal;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.TransferPipe;
@@ -8082,7 +8084,7 @@
                     // Adjust the source bounds by the insets for the transition down
                     final Rect sourceBounds = new Rect(r.pictureInPictureArgs.getSourceRectHint());
                     mStackSupervisor.moveActivityToPinnedStackLocked(r, sourceBounds, aspectRatio,
-                            true /* moveHomeStackToFront */, "enterPictureInPictureMode");
+                            "enterPictureInPictureMode");
                     final PinnedActivityStack stack = r.getStack();
                     stack.setPictureInPictureAspectRatio(aspectRatio);
                     stack.setPictureInPictureActions(actions);
@@ -10230,11 +10232,7 @@
                 Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode");
                 return;
             }
-            final ActivityRecord prev = mStackSupervisor.topRunningActivityLocked();
-            if (prev != null) {
-                task.setTaskToReturnTo(prev);
-            }
-            mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options, "moveTaskToFront",
+            mStackSupervisor.findTaskToMoveToFront(task, flags, options, "moveTaskToFront",
                     false /* forceNonResizable */);
 
             final ActivityRecord topActivity = task.getTopActivity();
@@ -12332,6 +12330,9 @@
                     + android.Manifest.permission.SHUTDOWN);
         }
 
+        // TODO: Where should the corresponding '1' (start) write go?
+        StatsLog.write(StatsLog.DEVICE_ON_STATUS_CHANGED, 0);
+
         boolean timedout = false;
 
         synchronized(this) {
@@ -13514,6 +13515,7 @@
                     stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid,
                             sourcePkg != null ? sourcePkg : rec.key.packageName);
                 pkg.noteWakeupAlarmLocked(tag);
+                StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, sourceUid >= 0 ? sourceUid : uid);
             }
         }
     }
@@ -14085,6 +14087,23 @@
             }
             mStackSupervisor.resumeFocusedStackTopActivityLocked();
             mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
+
+            BinderInternal.nSetBinderProxyCountEnabled(true);
+            BinderInternal.setBinderProxyCountCallback(
+                    new BinderInternal.BinderProxyLimitListener() {
+                        @Override
+                        public void onLimitReached(int uid) {
+                            Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
+                                    + Process.myUid());
+                            if (uid == Process.SYSTEM_UID) {
+                                Slog.i(TAG, "Skipping kill (uid is SYSTEM)");
+                            } else {
+                                killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
+                                        "Too many Binders sent to SYSTEM");
+                            }
+                        }
+                    }, mHandler);
+
             traceLog.traceEnd(); // ActivityManagerStartApps
             traceLog.traceEnd(); // PhaseActivityManagerReady
         }
@@ -14902,6 +14921,19 @@
                         mRecentTasks.dump(pw, true /* dumpAll */, dumpPackage);
                     }
                 }
+            } else if ("binder-proxies".equals(cmd)) {
+                if (opti >= args.length) {
+                    dumpBinderProxiesCounts(pw, BinderInternal.nGetBinderProxyPerUidCounts(),
+                            "Counts of Binder Proxies held by SYSTEM");
+                } else {
+                    String uid = args[opti];
+                    opti++;
+                    // Ensure Binder Proxy Count is as up to date as possible
+                    System.gc();
+                    System.runFinalization();
+                    System.gc();
+                    pw.println(BinderInternal.nGetBinderProxyCount(Integer.parseInt(uid)));
+                }
             } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
                 String[] newArgs;
                 String name;
@@ -15420,6 +15452,34 @@
         return printed;
     }
 
+    boolean dumpBinderProxiesCounts(PrintWriter pw, SparseIntArray counts, String header) {
+        if(counts != null) {
+            pw.println(header);
+            for (int i = 0; i < counts.size(); i++) {
+                final int uid = counts.keyAt(i);
+                final int binderCount = counts.valueAt(i);
+                pw.print("    UID ");
+                pw.print(uid);
+                pw.print(", binder count = ");
+                pw.print(binderCount);
+                pw.print(", package(s)= ");
+                final String[] pkgNames = mContext.getPackageManager().getPackagesForUid(uid);
+                if (pkgNames != null) {
+                    for (int j = 0; j < pkgNames.length; j++) {
+                        pw.print(pkgNames[j]);
+                        pw.print("; ");
+                    }
+                } else {
+                    pw.print("NO PACKAGE NAME FOUND");
+                }
+                pw.println();
+            }
+            pw.println();
+            return true;
+        }
+        return false;
+    }
+
     void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
         boolean needSep = false;
@@ -15625,7 +15685,8 @@
             }
             pw.println("  mPreviousProcess: " + mPreviousProcess);
         }
-        if (dumpAll) {
+        if (dumpAll && (mPreviousProcess == null || dumpPackage == null
+                || mPreviousProcess.pkgList.containsKey(dumpPackage))) {
             StringBuilder sb = new StringBuilder(128);
             sb.append("  mPreviousProcessVisibleTime: ");
             TimeUtils.formatDuration(mPreviousProcessVisibleTime, sb);
@@ -15644,7 +15705,9 @@
             mStackSupervisor.dumpDisplayConfigs(pw, "  ");
         }
         if (dumpAll) {
-            pw.println("  mConfigWillChange: " + getFocusedStack().mConfigWillChange);
+            if (dumpPackage == null) {
+                pw.println("  mConfigWillChange: " + getFocusedStack().mConfigWillChange);
+            }
             if (mCompatModePackages.getPackages().size() > 0) {
                 boolean printed = false;
                 for (Map.Entry<String, Integer> entry
@@ -15724,8 +15787,8 @@
                 pw.println("  mRunningVoice=" + mRunningVoice);
                 pw.println("  mVoiceWakeLock" + mVoiceWakeLock);
             }
+            pw.println("  mVrController=" + mVrController);
         }
-        pw.println("  mVrController=" + mVrController);
         if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
                 || mOrigWaitForDebugger) {
             if (dumpPackage == null || dumpPackage.equals(mDebugApp)
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index f03d2d5..4cf2794 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -222,6 +222,8 @@
                     return runSetInactive(pw);
                 case "get-inactive":
                     return runGetInactive(pw);
+                case "set-standby-bucket":
+                    return runSetStandbyBucket(pw);
                 case "send-trim-memory":
                     return runSendTrimMemory(pw);
                 case "display":
@@ -1824,6 +1826,27 @@
         return 0;
     }
 
+    int runSetStandbyBucket(PrintWriter pw) throws RemoteException {
+        int userId = UserHandle.USER_CURRENT;
+
+        String opt;
+        while ((opt=getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+        String packageName = getNextArgRequired();
+        String value = getNextArgRequired();
+
+        IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService(
+                Context.USAGE_STATS_SERVICE));
+        usm.setAppStandbyBucket(packageName, Integer.parseInt(value), userId);
+        return 0;
+    }
+
     int runGetInactive(PrintWriter pw) throws RemoteException {
         int userId = UserHandle.USER_CURRENT;
 
@@ -2571,6 +2594,8 @@
             pw.println("      Sets the inactive state of an app.");
             pw.println("  get-inactive [--user <USER_ID>] <PACKAGE>");
             pw.println("      Returns the inactive state of an app.");
+            pw.println("  set-standby-bucket [--user <USER_ID>] <PACKAGE> <BUCKET>");
+            pw.println("      Puts an app in the standby bucket.");
             pw.println("  send-trim-memory [--user <USER_ID>] <PROCESS>");
             pw.println("          [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]");
             pw.println("      Send a memory trim event to a <PROCESS>.  May also supply a raw trim int level.");
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index b885eab..b47f819 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -277,6 +277,7 @@
     ActivityState state;    // current state we are in
     Bundle  icicle;         // last saved activity state
     PersistableBundle persistentState; // last persistently saved activity state
+    // TODO: See if this is still needed.
     boolean frontOfTask;    // is this the root activity of its task?
     boolean launchFailed;   // set if a launched failed, to abort on 2nd try
     boolean haveState;      // have we gotten the last activity state?
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index f05243b..bafcce7 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -16,13 +16,10 @@
 
 package com.android.server.am;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -107,7 +104,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Binder;
@@ -346,7 +342,6 @@
     private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
     private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
     private final Rect mTmpRect2 = new Rect();
-    private final Point mTmpSize = new Point();
 
     /** Run all ActivityStacks through this */
     protected final ActivityStackSupervisor mStackSupervisor;
@@ -505,7 +500,7 @@
         removeFromDisplay();
         mTmpRect2.setEmpty();
         postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
-        adjustFocusToNextFocusableStackLocked("reparent", true /* allowFocusSelf */);
+        adjustFocusToNextFocusableStack("reparent", true /* allowFocusSelf */);
         mStackSupervisor.resumeFocusedStackTopActivityLocked();
         // Update visibility of activities before notifying WM. This way it won't try to resize
         // windows that are no longer visible.
@@ -834,6 +829,12 @@
         return mDisplayId == DEFAULT_DISPLAY;
     }
 
+    private boolean returnsToHomeStack() {
+        return !inMultiWindowMode()
+                && !mTaskHistory.isEmpty()
+                && mTaskHistory.get(0).returnsToHomeStack();
+    }
+
     void moveToFront(String reason) {
         moveToFront(reason, null);
     }
@@ -847,6 +848,12 @@
             return;
         }
 
+        if (!isActivityTypeHome() && returnsToHomeStack()) {
+            // Make sure the home stack is behind this stack since that is where we should return to
+            // when this stack is no longer visible.
+            mStackSupervisor.moveHomeStackToFront(reason + " returnToHome");
+        }
+
         getDisplay().positionChildAtTop(this);
         mStackSupervisor.setFocusStackUnchecked(reason, this);
         if (task != null) {
@@ -1498,11 +1505,9 @@
      * needed. A stack is considered translucent if it don't contain a visible or
      * starting (about to be visible) activity that is fullscreen (opaque).
      * @param starting The currently starting activity or null if there is none.
-     * TODO: Can be removed once we are no longer using returnToType for back functionality
-     * @param stackBehind The stack directly behind this one.
      */
     @VisibleForTesting
-    boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) {
+    boolean isStackTranslucent(ActivityRecord starting) {
         if (!isAttached() || mForceHidden) {
             return true;
         }
@@ -1529,16 +1534,6 @@
                     // that is visible.
                     return false;
                 }
-
-                final boolean stackBehindHomeOrRecent = stackBehind != null
-                        && stackBehind.isHomeOrRecentsStack();
-                if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack()
-                        && !stackBehindHomeOrRecent && !isActivityTypeAssistant()) {
-                    // Stack isn't translucent if it's top activity should have the home stack
-                    // behind it and the stack currently behind it isn't the home or recents stack
-                    // or the assistant stack.
-                    return false;
-                }
             }
         }
         return true;
@@ -1590,7 +1585,7 @@
             final ActivityStack stackBehind = i > 0 ? display.getChildAt(i - 1) : null;
 
             if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
-                if (other.isStackTranslucent(starting, stackBehind)) {
+                if (other.isStackTranslucent(starting)) {
                     // Can be visible behind a translucent fullscreen stack.
                     continue;
                 }
@@ -1598,7 +1593,7 @@
             } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                     && !gotOpaqueSplitScreenPrimary) {
                 gotOpaqueSplitScreenPrimary =
-                        !other.isStackTranslucent(starting, stackBehind);
+                        !other.isStackTranslucent(starting);
                 if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                         && gotOpaqueSplitScreenPrimary) {
                     // Can not be visible behind another opaque stack in split-screen-primary mode.
@@ -1607,7 +1602,7 @@
             } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                     && !gotOpaqueSplitScreenSecondary) {
                 gotOpaqueSplitScreenSecondary =
-                        !other.isStackTranslucent(starting, stackBehind);
+                        !other.isStackTranslucent(starting);
                 if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                         && gotOpaqueSplitScreenSecondary) {
                     // Can not be visible behind another opaque stack in split-screen-secondary mode.
@@ -1643,6 +1638,7 @@
      * Make sure that all activities that need to be visible (that is, they
      * currently can be seen by the user) actually are.
      */
+    // TODO: Should be re-worked based on the fact that each task as a stack in most cases.
     final void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges,
             boolean preserveWindows) {
         mTopActivityOccludesKeyguard = false;
@@ -1687,7 +1683,7 @@
                             visibleIgnoringKeyguard, isTop);
                     if (visibleIgnoringKeyguard) {
                         behindFullscreenActivity = updateBehindFullscreen(!stackShouldBeVisible,
-                                behindFullscreenActivity, task, r);
+                                behindFullscreenActivity, r);
                     }
                     if (reallyVisible) {
                         if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r
@@ -1746,16 +1742,6 @@
                     // show activities in the next application stack behind them vs. another
                     // task in the home stack like recents.
                     behindFullscreenActivity = true;
-                } else if (windowingMode == WINDOWING_MODE_FULLSCREEN
-                        || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-                    if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping after task=" + task
-                            + " returning to non-application type=" + task.getTaskToReturnTo());
-                    // Once we reach a fullscreen stack task that has a running activity and should
-                    // return to another stack task, then no other activities behind that one should
-                    // be visible.
-                    if (task.topRunningActivityLocked() != null && !task.returnsToStandardTask()) {
-                        behindFullscreenActivity = true;
-                    }
                 }
             }
 
@@ -1791,24 +1777,6 @@
     }
 
     /**
-     * Returns true if we try to maintain focus in the current stack when the top activity finishes.
-     */
-    private boolean keepFocusInStackIfPossible() {
-        final int windowingMode = getWindowingMode();
-        return windowingMode == WINDOWING_MODE_FREEFORM
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                || windowingMode == WINDOWING_MODE_PINNED;
-    }
-
-    /**
-     * Returns true if the top task in the task is allowed to return home when finished and
-     * there are other tasks in the stack.
-     */
-    boolean allowTopTaskToReturnHome() {
-        return !inPinnedWindowingMode();
-    }
-
-    /**
      * @return the top most visible activity that wants to dismiss Keyguard
      */
     ActivityRecord getTopDismissingKeyguardActivity() {
@@ -1969,18 +1937,13 @@
     }
 
     private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity,
-            TaskRecord task, ActivityRecord r) {
+            ActivityRecord r) {
         if (r.fullscreen) {
             if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
                         + " stackInvisible=" + stackInvisible
                         + " behindFullscreenActivity=" + behindFullscreenActivity);
             // At this point, nothing else needs to be shown in this task.
             behindFullscreenActivity = true;
-        } else if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack()) {
-            if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r
-                    + " stackInvisible=" + stackInvisible
-                    + " behindFullscreenActivity=" + behindFullscreenActivity);
-            behindFullscreenActivity = true;
         }
         return behindFullscreenActivity;
     }
@@ -2139,7 +2102,7 @@
         final boolean hasRunningActivity = next != null;
 
         // TODO: Maybe this entire condition can get removed?
-        if (hasRunningActivity && getDisplay() == null) {
+        if (hasRunningActivity && !isAttached()) {
             return false;
         }
 
@@ -2169,28 +2132,6 @@
             return false;
         }
 
-        final TaskRecord nextTask = next.getTask();
-        final TaskRecord prevTask = prev != null ? prev.getTask() : null;
-        if (prevTask != null && prevTask.getStack() == this &&
-                prevTask.isOverHomeStack() && prev.finishing && prev.frontOfTask) {
-            if (DEBUG_STACK)  mStackSupervisor.validateTopActivitiesLocked();
-            if (prevTask == nextTask) {
-                prevTask.setFrontOfTask();
-            } else if (prevTask != topTask()) {
-                // This task is going away but it was supposed to return to the home stack.
-                // Now the task above it has to return to the home task instead.
-                final int taskNdx = mTaskHistory.indexOf(prevTask) + 1;
-                mTaskHistory.get(taskNdx).setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            } else if (!isOnHomeDisplay()) {
-                return false;
-            } else if (!isActivityTypeHome()){
-                if (DEBUG_STATES) Slog.d(TAG_STATES,
-                        "resumeTopActivityLocked: Launching home next");
-                return isOnHomeDisplay() &&
-                        mStackSupervisor.resumeHomeStackTask(prev, "prevFinished");
-            }
-        }
-
         // If we are sleeping, and there is no resumed activity, and the top
         // activity is paused, well that is the state we want.
         if (shouldSleepOrShutDownActivities()
@@ -2574,7 +2515,7 @@
 
     private boolean resumeTopActivityInNextFocusableStack(ActivityRecord prev,
             ActivityOptions options, String reason) {
-        if (adjustFocusToNextFocusableStackLocked(reason)) {
+        if (adjustFocusToNextFocusableStack(reason)) {
             // Try to move focus to the next visible stack with a running activity if this
             // stack is not covering the entire screen or is on a secondary display (with no home
             // stack).
@@ -2648,7 +2589,6 @@
     }
 
     private void insertTaskAtTop(TaskRecord task, ActivityRecord starting) {
-        updateTaskReturnToForTopInsertion(task);
         // TODO: Better place to put all the code below...may be addTask...
         mTaskHistory.remove(task);
         // Now put task at top.
@@ -2659,57 +2599,6 @@
                 true /* includingParents */);
     }
 
-    /**
-     * Updates the {@param task}'s return type before it is moved to the top.
-     */
-    private void updateTaskReturnToForTopInsertion(TaskRecord task) {
-        boolean isLastTaskOverHome = false;
-        // If the moving task is over the home or assistant stack, transfer its return type to next
-        // task so that they return to the same stack
-        if (task.isOverHomeStack() || task.isOverAssistantStack()) {
-            final TaskRecord nextTask = getNextTask(task);
-            if (nextTask != null) {
-                nextTask.setTaskToReturnTo(task.getTaskToReturnTo());
-            } else {
-                isLastTaskOverHome = true;
-            }
-        }
-
-        // If this is not on the default display, then just set the return type to application
-        if (!isOnHomeDisplay()) {
-            task.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
-            return;
-        }
-
-        final ActivityStack lastStack = mStackSupervisor.getLastStack();
-
-        // If there is no last task, do not set task to return to
-        if (lastStack == null) {
-            return;
-        }
-
-        // If the task was launched from the assistant stack, set the return type to assistant
-        if (lastStack.isActivityTypeAssistant()) {
-            task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
-            return;
-        }
-
-        // If this is being moved to the top by another activity or being launched from the home
-        // activity, set mTaskToReturnTo accordingly.
-        final boolean fromHomeOrRecents = lastStack.isHomeOrRecentsStack();
-        final TaskRecord topTask = lastStack.topTask();
-        if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask() != task)) {
-            // If it's a last task over home - we default to keep its return to type not to
-            // make underlying task focused when this one will be finished.
-            int returnToType = isLastTaskOverHome
-                    ? task.getTaskToReturnTo() : ACTIVITY_TYPE_STANDARD;
-            if (fromHomeOrRecents && allowTopTaskToReturnHome()) {
-                returnToType = topTask == null ? ACTIVITY_TYPE_HOME : topTask.getActivityType();
-            }
-            task.setTaskToReturnTo(returnToType);
-        }
-    }
-
     final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
             boolean newTask, boolean keepCurTransition, ActivityOptions options) {
         TaskRecord rTask = r.getTask();
@@ -2979,7 +2868,7 @@
                 }
 
                 mWindowContainerController.positionChildAtBottom(
-                        targetTask.getWindowContainerController());
+                        targetTask.getWindowContainerController(), false /* includingParents */);
                 replyChainEnd = -1;
             } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) {
                 // If the activity should just be removed -- either
@@ -3215,7 +3104,7 @@
     }
 
     /** Returns true if the task is one of the task finishing on-top of the top running task. */
-    boolean isATopFinishingTask(TaskRecord task) {
+    private boolean isATopFinishingTask(TaskRecord task) {
         for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
             final TaskRecord current = mTaskHistory.get(i);
             final ActivityRecord r = current.topRunningActivityLocked();
@@ -3230,7 +3119,7 @@
         return false;
     }
 
-    private void adjustFocusedActivityStackLocked(ActivityRecord r, String reason) {
+    private void adjustFocusedActivityStack(ActivityRecord r, String reason) {
         if (!mStackSupervisor.isFocusedStack(this) ||
                 ((mResumedActivity != r) && (mResumedActivity != null))) {
             return;
@@ -3239,66 +3128,44 @@
         final ActivityRecord next = topRunningActivityLocked();
         final String myReason = reason + " adjustFocus";
 
-        if (next != r) {
-            if (next != null && keepFocusInStackIfPossible() && isFocusable()) {
-                // For freeform, docked, and pinned stacks we always keep the focus within the
-                // stack as long as there is a running activity.
-                return;
-            } else {
-                // Task is not guaranteed to be non-null. For example, destroying the
-                // {@link ActivityRecord} will disassociate the task from the activity.
-                final TaskRecord task = r.getTask();
-
-                if (task == null) {
-                    throw new IllegalStateException("activity no longer associated with task:" + r);
-                }
-
-                final boolean isAssistantOrOverAssistant =
-                        task.getStack().isActivityTypeAssistant() || task.isOverAssistantStack();
-                if (r.frontOfTask && isATopFinishingTask(task)
-                        && (task.isOverHomeStack() || isAssistantOrOverAssistant)) {
-                    // For non-fullscreen or assistant stack, we want to move the focus to the next
-                    // visible stack to prevent the home screen from moving to the top and obscuring
-                    // other visible stacks.
-                    if ((!mFullscreen || isAssistantOrOverAssistant)
-                            && adjustFocusToNextFocusableStackLocked(myReason)) {
-                        return;
-                    }
-                    // Move the home stack to the top if this stack is fullscreen or there is no
-                    // other visible stack.
-                    if (task.isOverHomeStack() &&
-                            mStackSupervisor.moveHomeStackTaskToTop(myReason)) {
-                        // Activity focus was already adjusted. Nothing else to do...
-                        return;
-                    }
-                }
-            }
+        if (next == r) {
+            mStackSupervisor.moveFocusableActivityStackToFrontLocked(
+                    mStackSupervisor.topRunningActivityLocked(), myReason);
+            return;
         }
 
-        mStackSupervisor.moveFocusableActivityStackToFrontLocked(
-                mStackSupervisor.topRunningActivityLocked(), myReason);
+        if (next != null && isFocusable()) {
+            // Keep focus in stack if we have a top running activity and are focusable.
+            return;
+        }
+
+        // Task is not guaranteed to be non-null. For example, destroying the
+        // {@link ActivityRecord} will disassociate the task from the activity.
+        final TaskRecord task = r.getTask();
+
+        if (task == null) {
+            throw new IllegalStateException("activity no longer associated with task:" + r);
+        }
+
+        // Move focus to next focusable stack if possible.
+        if (adjustFocusToNextFocusableStack(myReason)) {
+            return;
+        }
+
+        // Whatever...go home.
+        mStackSupervisor.moveHomeStackTaskToTop(myReason);
     }
 
     /** Find next proper focusable stack and make it focused. */
-    private boolean adjustFocusToNextFocusableStackLocked(String reason) {
-        return adjustFocusToNextFocusableStackLocked(reason, false /* allowFocusSelf */);
+    private boolean adjustFocusToNextFocusableStack(String reason) {
+        return adjustFocusToNextFocusableStack(reason, false /* allowFocusSelf */);
     }
 
     /**
      * Find next proper focusable stack and make it focused.
      * @param allowFocusSelf Is the focus allowed to remain on the same stack.
      */
-    private boolean adjustFocusToNextFocusableStackLocked(String reason, boolean allowFocusSelf) {
-        if (isActivityTypeAssistant() && bottomTask() != null
-                && bottomTask().returnsToHomeTask()) {
-            // If the current stack is the assistant stack, then use the return-to type to determine
-            // whether to return to the home screen. This is needed to workaround an issue where
-            // launching a fullscreen task (and subequently returning from that task) will cause
-            // the fullscreen stack to be found as the next focusable stack below, even if the
-            // assistant was launched over home.
-            return mStackSupervisor.moveHomeStackTaskToTop(reason);
-        }
-
+    private boolean adjustFocusToNextFocusableStack(String reason, boolean allowFocusSelf) {
         final ActivityStack stack = mStackSupervisor.getNextFocusableStackLocked(
                 allowFocusSelf ? null : this);
         final String myReason = reason + " adjustFocusToNextFocusableStack";
@@ -3308,22 +3175,12 @@
 
         final ActivityRecord top = stack.topRunningActivityLocked();
 
-        if (stack.isHomeOrRecentsStack() && (top == null || !top.visible)) {
+        if (stack.isActivityTypeHome() && (top == null || !top.visible)) {
             // If we will be focusing on the home stack next and its current top activity isn't
-            // visible, then use the task return to value to determine the home task to display
-            // next.
+            // visible, then use the move the home stack task to top to make the activity visible.
             return mStackSupervisor.moveHomeStackTaskToTop(reason);
         }
 
-        if (stack.isActivityTypeAssistant() && top != null
-                && top.getTask().returnsToHomeTask()) {
-            // It is possible for the home stack to not be directly underneath the assistant stack.
-            // For example, the assistant may start an activity in the fullscreen stack. Upon
-            // returning to the assistant stack, we must ensure that the home stack is underneath
-            // when appropriate.
-            mStackSupervisor.moveHomeStackTaskToTop("adjustAssistantReturnToHome");
-        }
-
         stack.moveToFront(myReason);
         return true;
     }
@@ -3338,7 +3195,7 @@
                     if (requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
                             "stop-no-history", false)) {
                         // If {@link requestFinishActivityLocked} returns {@code true},
-                        // {@link adjustFocusedActivityStackLocked} would have been already called.
+                        // {@link adjustFocusedActivityStack} would have been already called.
                         r.resumeKeyDispatchingLocked();
                         return;
                     }
@@ -3350,7 +3207,7 @@
         }
 
         if (r.app != null && r.app.thread != null) {
-            adjustFocusedActivityStackLocked(r, "stopActivity");
+            adjustFocusedActivityStack(r, "stopActivity");
             r.resumeKeyDispatchingLocked();
             try {
                 r.stopped = false;
@@ -3589,7 +3446,7 @@
 
             r.pauseKeyDispatchingLocked();
 
-            adjustFocusedActivityStackLocked(r, "finishActivity");
+            adjustFocusedActivityStack(r, "finishActivity");
 
             finishActivityResultsLocked(r, resultCode, resultData);
 
@@ -3756,7 +3613,21 @@
         }
     }
 
-    final boolean shouldUpRecreateTaskLocked(ActivityRecord srec, String destAffinity) {
+    /** @return true if the stack behind this one is a standard activity type. */
+    boolean inFrontOfStandardStack() {
+        final ActivityDisplay display = getDisplay();
+        if (display == null) {
+            return false;
+        }
+        final int index = display.getIndexOf(this);
+        if (index == 0) {
+            return false;
+        }
+        final ActivityStack stackBehind = display.getChildAt(index - 1);
+        return stackBehind.isActivityTypeStandard();
+    }
+
+    boolean shouldUpRecreateTaskLocked(ActivityRecord srec, String destAffinity) {
         // Basic case: for simple app-centric recents, we need to recreate
         // the task if the affinity has changed.
         if (srec == null || srec.getTask().affinity == null ||
@@ -3768,10 +3639,9 @@
         // of a document, unless simply finishing it will return them to the the
         // correct app behind.
         final TaskRecord task = srec.getTask();
-        if (srec.frontOfTask && task != null && task.getBaseIntent() != null
-                && task.getBaseIntent().isDocument()) {
+        if (srec.frontOfTask && task.getBaseIntent() != null && task.getBaseIntent().isDocument()) {
             // Okay, this activity is at the root of its task.  What to do, what to do...
-            if (!task.returnsToStandardTask()) {
+            if (!inFrontOfStandardStack()) {
                 // Finishing won't return to an application, so we need to recreate.
                 return true;
             }
@@ -3971,11 +3841,6 @@
                                 + " onlyHasTaskOverlays=" + onlyHasTaskOverlays);
             }
 
-            if (mStackSupervisor.isFocusedStack(this) && task == topTask() &&
-                    task.isOverHomeStack()) {
-                mStackSupervisor.moveHomeStackTaskToTop(reason);
-            }
-
             // The following block can be executed multiple times if there is more than one overlay.
             // {@link ActivityStackSupervisor#removeTaskByIdLocked} handles this by reverse lookup
             // of the task by id and exiting early if not found.
@@ -4497,60 +4362,19 @@
 
         if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to back transition: task=" + taskId);
 
-        boolean prevIsHome = false;
+        mTaskHistory.remove(tr);
+        mTaskHistory.add(0, tr);
+        updateTaskMovement(tr, false);
 
-        // If true, we should resume the home activity next if the task we are moving to the
-        // back is over the home stack. We force to false if the task we are moving to back
-        // is the home task and we don't want it resumed after moving to the back.
-        final boolean canGoHome = !tr.isActivityTypeHome() && tr.isOverHomeStack();
-        if (canGoHome) {
-            final TaskRecord nextTask = getNextTask(tr);
-            if (nextTask != null) {
-                nextTask.setTaskToReturnTo(tr.getTaskToReturnTo());
-            } else {
-                prevIsHome = true;
-            }
-        }
-
-        boolean requiresMove = mTaskHistory.indexOf(tr) != 0;
-        if (requiresMove) {
-            mTaskHistory.remove(tr);
-            mTaskHistory.add(0, tr);
-            updateTaskMovement(tr, false);
-
-            mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
-            mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController());
-        }
+        mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
+        mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController(),
+                true /* includingParents */);
 
         if (inPinnedWindowingMode()) {
             mStackSupervisor.removeStack(this);
             return true;
         }
 
-        // Otherwise, there is an assumption that moving a task to the back moves it behind the
-        // home activity. We make sure here that some activity in the stack will launch home.
-        int numTasks = mTaskHistory.size();
-        for (int taskNdx = numTasks - 1; taskNdx >= 1; --taskNdx) {
-            final TaskRecord task = mTaskHistory.get(taskNdx);
-            if (task.isOverHomeStack()) {
-                break;
-            }
-            if (taskNdx == 1) {
-                // Set the last task before tr to go to home.
-                task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            }
-        }
-
-        final TaskRecord task = mResumedActivity != null ? mResumedActivity.getTask() : null;
-        if (prevIsHome || (task == tr && canGoHome) || (numTasks <= 1 && isOnHomeDisplay())) {
-            if (!mService.mBooting && !mService.mBooted) {
-                // Not ready yet!
-                return false;
-            }
-            tr.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
-            return mStackSupervisor.resumeHomeStackTask(null, "moveTaskToBack");
-        }
-
         mStackSupervisor.resumeFocusedStackTopActivityLocked();
         return true;
     }
@@ -4987,14 +4811,6 @@
             onActivityRemovedFromStack(record);
         }
 
-        final int taskNdx = mTaskHistory.indexOf(task);
-        final int topTaskNdx = mTaskHistory.size() - 1;
-        if (task.isOverHomeStack() && taskNdx < topTaskNdx) {
-            final TaskRecord nextTask = mTaskHistory.get(taskNdx + 1);
-            if (!nextTask.isOverHomeStack() && !nextTask.isOverAssistantStack()) {
-                nextTask.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            }
-        }
         mTaskHistory.remove(task);
         removeActivitiesFromLRUListLocked(task);
         updateTaskMovement(task, true);
@@ -5024,7 +4840,7 @@
             if (isOnHomeDisplay() && mode != REMOVE_TASK_MODE_MOVING_TO_TOP
                     && mStackSupervisor.isFocusedStack(this)) {
                 String myReason = reason + " leftTaskHistoryEmpty";
-                if (mFullscreen || !adjustFocusToNextFocusableStackLocked(myReason)) {
+                if (mFullscreen || !adjustFocusToNextFocusableStack(myReason)) {
                     mStackSupervisor.moveHomeStackToFront(myReason);
                 }
             }
@@ -5053,24 +4869,14 @@
         addTask(task, toTop, "createTaskRecord");
         final boolean isLockscreenShown = mService.mStackSupervisor.mKeyguardController
                 .isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
-        if (!layoutTaskInStack(task, info.windowLayout) && mBounds != null && task.isResizeable()
-                && !isLockscreenShown) {
+        if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout)
+                && mBounds != null && task.isResizeable() && !isLockscreenShown) {
             task.updateOverrideConfiguration(mBounds);
         }
         task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
         return task;
     }
 
-    boolean layoutTaskInStack(TaskRecord task, ActivityInfo.WindowLayout windowLayout) {
-        if (!task.inFreeformWindowingMode()) {
-            return false;
-        }
-        mStackSupervisor.getLaunchingTaskPositioner()
-                .updateDefaultBounds(task, mTaskHistory, windowLayout);
-
-        return true;
-    }
-
     ArrayList<TaskRecord> getAllTasks() {
         return new ArrayList<>(mTaskHistory);
     }
@@ -5097,10 +4903,6 @@
         mTaskHistory.add(position, task);
         task.setStack(this);
 
-        if (toTop) {
-            updateTaskReturnToForTopInsertion(task);
-        }
-
         updateTaskMovement(task, toTop);
 
         postAddTask(task, prevStack, schedulePictureInPictureModeChange);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 7a4a0d4..14c0eea 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -287,7 +287,7 @@
     WindowManagerService mWindowManager;
     DisplayManager mDisplayManager;
 
-    LaunchingTaskPositioner mTaskPositioner = new LaunchingTaskPositioner();
+    private final LaunchingBoundsController mLaunchingBoundsController;
 
     /** Counter for next free stack ID to use for dynamic activity stacks. */
     private int mNextFreeStackId = 0;
@@ -568,6 +568,9 @@
         mHandler = new ActivityStackSupervisorHandler(looper);
         mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext);
         mKeyguardController = new KeyguardController(service, this);
+
+        mLaunchingBoundsController = new LaunchingBoundsController();
+        mLaunchingBoundsController.registerDefaultPositioners(this);
     }
 
     void setRecentTasks(RecentTasks recentTasks) {
@@ -678,10 +681,6 @@
             return false;
         }
 
-        if (prev != null) {
-            prev.getTask().setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
-        }
-
         mHomeStack.moveHomeStackTaskToTop();
         ActivityRecord r = getHomeActivity();
         final String myReason = reason + " resumeHomeStackTask";
@@ -2090,21 +2089,26 @@
         }
     }
 
-    void findTaskToMoveToFrontLocked(TaskRecord task, int flags, ActivityOptions options,
+    void findTaskToMoveToFront(TaskRecord task, int flags, ActivityOptions options,
             String reason, boolean forceNonResizeable) {
+        final ActivityStack currentStack = task.getStack();
+        if (currentStack == null) {
+            Slog.e(TAG, "findTaskToMoveToFront: can't move task="
+                    + task + " to front. Stack is null");
+            return;
+        }
+
         if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) {
             mUserLeaving = true;
         }
-        if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
-            // Caller wants the home activity moved with it.  To accomplish this,
-            // we'll just indicate that this task returns to the home task.
-            task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-        }
-        final ActivityStack currentStack = task.getStack();
-        if (currentStack == null) {
-            Slog.e(TAG, "findTaskToMoveToFrontLocked: can't move task="
-                    + task + " to front. Stack is null");
-            return;
+
+        final ActivityRecord prev = topRunningActivityLocked();
+
+        if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0
+                || (prev != null && prev.isActivityTypeRecents())) {
+            // Caller wants the home activity moved with it or the previous task is recents in which
+            // case we always return home from the task we are moving to the front.
+            moveHomeStackToFront("findTaskToMoveToFront");
         }
 
         if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
@@ -2115,7 +2119,7 @@
 
             if (stack != currentStack) {
                 task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE, DEFER_RESUME,
-                        "findTaskToMoveToFrontLocked");
+                        "findTaskToMoveToFront");
                 stack = currentStack;
                 // moveTaskToStackUncheckedLocked() should already placed the task on top,
                 // still need moveTaskToFrontLocked() below for any transition settings.
@@ -2154,8 +2158,8 @@
                 || mService.mSupportsFreeformWindowManagement;
     }
 
-    LaunchingTaskPositioner getLaunchingTaskPositioner() {
-        return mTaskPositioner;
+    LaunchingBoundsController getLaunchingBoundsController() {
+        return mLaunchingBoundsController;
     }
 
     protected <T extends ActivityStack> T getStack(int stackId) {
@@ -2551,13 +2555,6 @@
                         final int returnToType =
                                 toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
                         final boolean isTopTask = i == (size - 1);
-                        if (inPinnedWindowingMode) {
-                            // Update the return-to to reflect where the pinned stack task was
-                            // moved from so that we retain the stack that was previously
-                            // visible if the pinned stack is recreated.
-                            // See moveActivityToPinnedStackLocked().
-                            task.setTaskToReturnTo(returnToType);
-                        }
                         // Defer resume until all the tasks have been moved to the fullscreen stack
                         task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
                                 isTopTask /* animate */, DEFER_RESUME,
@@ -3030,12 +3027,12 @@
         }
 
         moveActivityToPinnedStackLocked(r, null /* sourceBounds */, 0f /* aspectRatio */,
-                true /* moveHomeStackToFront */, "moveTopActivityToPinnedStack");
+                "moveTopActivityToPinnedStack");
         return true;
     }
 
     void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceHintBounds, float aspectRatio,
-            boolean moveHomeStackToFront, String reason) {
+            String reason) {
 
         mWindowManager.deferSurfaceLayout();
 
@@ -3061,17 +3058,6 @@
                     true /* allowResizeInDockedMode */, !DEFER_RESUME);
 
             if (task.mActivities.size() == 1) {
-                // There is only one activity in the task. So, we can just move the task over to
-                // the stack without re-parenting the activity in a different task.  We don't
-                // move the home stack forward if we are currently entering picture-in-picture
-                // while pausing because that changes the focused stack and may prevent the new
-                // starting activity from resuming.
-                if (moveHomeStackToFront && task.returnsToHomeTask()
-                        && (r.state == RESUMED || !r.supportsEnterPipOnTaskSwitch)) {
-                    // Move the home stack forward if the task we just moved to the pinned stack
-                    // was launched from home so home should be visible behind it.
-                    moveHomeStackToFront(reason);
-                }
                 // Defer resume until below, and do not schedule PiP changes until we animate below
                 task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, DEFER_RESUME,
                         false /* schedulePictureInPictureModeChange */, reason);
@@ -4513,6 +4499,10 @@
                         "startActivityFromRecents: Task " + taskId + " not found.");
             }
 
+            // We always want to return to the home activity instead of the recents activity from
+            // whatever is started from the recents activity, so move the home stack forward.
+            moveHomeStackToFront("startActivityFromRecents");
+
             // If the user must confirm credentials (e.g. when first launching a work app and the
             // Work Challenge is present) let startActivityInPackage handle the intercepting.
             if (!mService.mUserController.shouldConfirmCredentials(task.userId)
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 6f74d85..f6905c5 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -151,7 +151,7 @@
     private boolean mLaunchTaskBehind;
     private int mLaunchFlags;
 
-    private Rect mLaunchBounds;
+    private Rect mLaunchBounds = new Rect();
 
     private ActivityRecord mNotTop;
     private boolean mDoResume;
@@ -169,9 +169,6 @@
     private Intent mNewTaskIntent;
     private ActivityStack mSourceStack;
     private ActivityStack mTargetStack;
-    // Indicates that we moved other task and are going to put something on top soon, so
-    // we don't want to show it redundantly or accidentally change what's shown below.
-    private boolean mMovedOtherTask;
     private boolean mMovedToFront;
     private boolean mNoAnimation;
     private boolean mKeepCurTransition;
@@ -210,7 +207,7 @@
         mLaunchFlags = 0;
         mLaunchMode = INVALID_LAUNCH_MODE;
 
-        mLaunchBounds = null;
+        mLaunchBounds.setEmpty();
 
         mNotTop = null;
         mDoResume = false;
@@ -227,7 +224,6 @@
         mSourceStack = null;
 
         mTargetStack = null;
-        mMovedOtherTask = false;
         mMovedToFront = false;
         mNoAnimation = false;
         mKeepCurTransition = false;
@@ -1184,12 +1180,8 @@
                 mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.userId);
         mService.grantEphemeralAccessLocked(mStartActivity.userId, mIntent,
                 mStartActivity.appInfo.uid, UserHandle.getAppId(mCallingUid));
-        if (mSourceRecord != null) {
-            mStartActivity.getTask().setTaskToReturnTo(mSourceRecord);
-        }
         if (newTask) {
-            EventLog.writeEvent(
-                    EventLogTags.AM_CREATE_TASK, mStartActivity.userId,
+            EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.userId,
                     mStartActivity.getTask().taskId);
         }
         ActivityStack.logStartActivity(
@@ -1254,7 +1246,10 @@
 
         mPreferredDisplayId = getPreferedDisplayId(mSourceRecord, mStartActivity, options);
 
-        mLaunchBounds = getOverrideBounds(r, options, inTask);
+        mLaunchBounds.setEmpty();
+
+        mSupervisor.getLaunchingBoundsController().calculateBounds(inTask, null /*layout*/, r,
+                options, mLaunchBounds);
 
         mLaunchMode = r.launchMode;
 
@@ -1579,7 +1574,6 @@
                 if (mLaunchTaskBehind && mSourceRecord != null) {
                     intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
                 }
-                mMovedOtherTask = true;
 
                 // If the launch flags carry both NEW_TASK and CLEAR_TASK, the task's activities
                 // will be cleared soon by ActivityStarter in setTaskFromIntentActivity().
@@ -1644,7 +1638,6 @@
                     intentActivity.showStartingWindow(null /* prev */, false /* newTask */,
                             true /* taskSwitch */);
                 }
-                updateTaskReturnToType(intentActivity.getTask(), mLaunchFlags, focusStack);
             }
         }
         if (!mMovedToFront && mDoResume) {
@@ -1663,27 +1656,6 @@
         return intentActivity;
     }
 
-    private void updateTaskReturnToType(
-            TaskRecord task, int launchFlags, ActivityStack focusedStack) {
-        if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
-                == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
-            // Caller wants to appear on home activity.
-            task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            return;
-        } else if (focusedStack == null || focusedStack.isActivityTypeHome()) {
-            // Task will be launched over the home stack, so return home.
-            task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            return;
-        } else if (focusedStack != task.getStack() && focusedStack.isActivityTypeAssistant()) {
-            // Task was launched over the assistant stack, so return there
-            task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
-            return;
-        }
-
-        // Else we are coming from an application stack so return to an application.
-        task.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
-    }
-
     private void setTaskFromIntentActivity(ActivityRecord intentActivity) {
         if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                 == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
@@ -1700,11 +1672,6 @@
             task.performClearTaskLocked();
             mReuseTask = task;
             mReuseTask.setIntent(mStartActivity);
-
-            // When we clear the task - focus will be adjusted, which will bring another task
-            // to top before we launch the activity we need. This will temporary swap their
-            // mTaskToReturnTo values and we don't want to overwrite them accidentally.
-            mMovedOtherTask = true;
         } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                 || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
             ActivityRecord top = intentActivity.getTask().performClearTaskLocked(mStartActivity,
@@ -1725,7 +1692,7 @@
                     // Target stack got cleared when we all activities were removed above.
                     // Go ahead and reset it.
                     mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
-                            null /* bounds */, mLaunchFlags, mOptions);
+                            mLaunchFlags, mOptions);
                     mTargetStack.addTask(task,
                             !mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
                 }
@@ -1776,8 +1743,7 @@
 
     private int setTaskFromReuseOrCreateNewTask(
             TaskRecord taskToAffiliate, ActivityStack topStack) {
-        mTargetStack = computeStackFocus(
-                mStartActivity, true, mLaunchBounds, mLaunchFlags, mOptions);
+        mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions);
 
         // Do no move the target stack to front yet, as we might bail if
         // isLockTaskModeViolation fails below.
@@ -1806,15 +1772,6 @@
             return START_RETURN_LOCK_TASK_MODE_VIOLATION;
         }
 
-        if (!mMovedOtherTask) {
-            // If stack id is specified in activity options, usually it means that activity is
-            // launched not from currently focused stack (e.g. from SysUI or from shell) - in
-            // that case we check the target stack.
-            // TODO: Not sure I understand the value or use of the commented out code and the
-            // comment above. See if this causes any issues and why...
-            updateTaskReturnToType(mStartActivity.getTask(), mLaunchFlags,
-                    /*preferredLaunchStackId != INVALID_STACK_ID ? mTargetStack : */topStack);
-        }
         if (mDoResume) {
             mTargetStack.moveToFront("reuseOrNewTask");
         }
@@ -1962,7 +1919,7 @@
             return START_TASK_TO_FRONT;
         }
 
-        if (mLaunchBounds != null) {
+        if (!mLaunchBounds.isEmpty()) {
             // TODO: Shouldn't we already know what stack to use by the time we get here?
             ActivityStack stack = mSupervisor.getLaunchStack(null, null, mInTask, ON_TOP);
             if (stack != mInTask.getStack()) {
@@ -1985,7 +1942,7 @@
     }
 
     void updateBounds(TaskRecord task, Rect bounds) {
-        if (bounds == null) {
+        if (bounds.isEmpty()) {
             return;
         }
 
@@ -1998,8 +1955,7 @@
     }
 
     private void setTaskToCurrentTopOrCreateNewTask() {
-        mTargetStack = computeStackFocus(mStartActivity, false, null /* bounds */, mLaunchFlags,
-                mOptions);
+        mTargetStack = computeStackFocus(mStartActivity, false, mLaunchFlags, mOptions);
         if (mDoResume) {
             mTargetStack.moveToFront("addingToTopTask");
         }
@@ -2062,8 +2018,8 @@
         }
     }
 
-    private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds,
-            int launchFlags, ActivityOptions aOptions) {
+    private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, int launchFlags,
+            ActivityOptions aOptions) {
         final TaskRecord task = r.getTask();
         ActivityStack stack = getLaunchStack(r, launchFlags, task, aOptions);
         if (stack != null) {
diff --git a/services/core/java/com/android/server/am/LaunchingActivityPositioner.java b/services/core/java/com/android/server/am/LaunchingActivityPositioner.java
new file mode 100644
index 0000000..5815e98
--- /dev/null
+++ b/services/core/java/com/android/server/am/LaunchingActivityPositioner.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner;
+
+/**
+ * An implementation of {@link LaunchingBoundsPositioner}, which applies the launch bounds specified
+ * inside {@link ActivityOptions#getLaunchBounds()}.
+ */
+public class LaunchingActivityPositioner implements LaunchingBoundsPositioner {
+    private final ActivityStackSupervisor mSupervisor;
+
+    LaunchingActivityPositioner(ActivityStackSupervisor activityStackSupervisor) {
+        mSupervisor = activityStackSupervisor;
+    }
+
+    @Override
+    public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout,
+            ActivityRecord activity, ActivityOptions options,  Rect current, Rect result) {
+        // We only care about figuring out bounds for activities.
+        if (activity == null) {
+            return RESULT_SKIP;
+        }
+
+        // Activity must be resizeable in the specified task.
+        if (!(mSupervisor.canUseActivityOptionsLaunchBounds(options)
+            && (activity.isResizeable() || (task != null && task.isResizeable())))) {
+            return RESULT_SKIP;
+        }
+
+        final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
+
+        // Bounds weren't valid.
+        if (bounds == null) {
+            return RESULT_SKIP;
+        }
+
+        result.set(bounds);
+
+        // When this is the most explicit position specification so we should not allow further
+        // modification of the position.
+        return RESULT_DONE;
+    }
+}
diff --git a/services/core/java/com/android/server/am/LaunchingBoundsController.java b/services/core/java/com/android/server/am/LaunchingBoundsController.java
new file mode 100644
index 0000000..8345ba6
--- /dev/null
+++ b/services/core/java/com/android/server/am/LaunchingBoundsController.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import android.annotation.IntDef;
+import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo.WindowLayout;
+import android.graphics.Rect;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP;
+
+/**
+ * {@link LaunchingBoundsController} calculates the launch bounds by coordinating between registered
+ * {@link LaunchingBoundsPositioner}.
+ */
+class LaunchingBoundsController {
+    private final List<LaunchingBoundsPositioner> mPositioners = new ArrayList<>();
+
+    // Temporary {@link Rect} for calculations. This is kept separate from {@code mTmpCurrent} and
+    // {@code mTmpResult} to prevent clobbering values.
+    private final Rect mTmpRect = new Rect();
+
+    private final Rect mTmpCurrent = new Rect();
+    private final Rect mTmpResult = new Rect();
+
+    /**
+     * Creates a {@link LaunchingBoundsController} with default registered
+     * {@link LaunchingBoundsPositioner}s.
+     */
+    void registerDefaultPositioners(ActivityStackSupervisor supervisor) {
+        // {@link LaunchingTaskPositioner} handles window layout preferences.
+        registerPositioner(new LaunchingTaskPositioner());
+
+        // {@link LaunchingActivityPositioner} is the most specific positioner and thus should be
+        // registered last (applied first) out of the defaults.
+        registerPositioner(new LaunchingActivityPositioner(supervisor));
+    }
+
+    /**
+     * Returns the position calculated by the registered positioners
+     * @param task      The {@link TaskRecord} currently being positioned.
+     * @param layout    The specified {@link WindowLayout}.
+     * @param activity  The {@link ActivityRecord} currently being positioned.
+     * @param options   The {@link ActivityOptions} specified for the activity.
+     * @param result    The resulting bounds. If no bounds are set, {@link Rect#isEmpty()} will be
+     *                  true.
+     */
+    void calculateBounds(TaskRecord task, WindowLayout layout, ActivityRecord activity,
+            ActivityOptions options, Rect result) {
+        result.setEmpty();
+
+        // We start at the last registered {@link LaunchingBoundsPositioner} as this represents
+        // The positioner closest to the product level. Moving back through the list moves closer to
+        // the platform logic.
+        for (int i = mPositioners.size() - 1; i >= 0; --i) {
+            mTmpResult.setEmpty();
+            mTmpCurrent.set(result);
+            final LaunchingBoundsPositioner positioner = mPositioners.get(i);
+
+            switch(positioner.onCalculateBounds(task, layout, activity, options, mTmpCurrent,
+                    mTmpResult)) {
+                case RESULT_SKIP:
+                    // Do not apply any results when we are told to skip
+                    continue;
+                case RESULT_DONE:
+                    // Set result and return immediately.
+                    result.set(mTmpResult);
+                    return;
+                case RESULT_CONTINUE:
+                    // Set result and continue
+                    result.set(mTmpResult);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * A convenience method for laying out a task.
+     * @return {@code true} if bounds were set on the task. {@code false} otherwise.
+     */
+    boolean layoutTask(TaskRecord task, WindowLayout layout) {
+        calculateBounds(task, layout, null /*activity*/, null /*options*/, mTmpRect);
+
+        if (mTmpRect.isEmpty()) {
+            return false;
+        }
+
+        task.updateOverrideConfiguration(mTmpRect);
+
+        return true;
+    }
+
+    /**
+     * Adds a positioner to participate in future bounds calculation. Note that the last registered
+     * {@link LaunchingBoundsPositioner} will be the first to calculate the bounds.
+     */
+    void registerPositioner(LaunchingBoundsPositioner positioner) {
+        if (mPositioners.contains(positioner)) {
+            return;
+        }
+
+        mPositioners.add(positioner);
+    }
+
+    /**
+     * An interface implemented by those wanting to participate in bounds calculation.
+     */
+    interface LaunchingBoundsPositioner {
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({RESULT_SKIP, RESULT_DONE, RESULT_CONTINUE})
+        @interface Result {}
+
+        // Returned when the positioner does not want to influence the bounds calculation
+        int RESULT_SKIP = 0;
+        // Returned when the positioner has changed the bounds and would like its results to be the
+        // final bounds applied.
+        int RESULT_DONE = 1;
+        // Returned when the positioner has changed the bounds but is okay with other positioners
+        // influencing the bounds.
+        int RESULT_CONTINUE = 2;
+
+        /**
+         * Called when asked to calculate bounds.
+         * @param task      The {@link TaskRecord} currently being positioned.
+         * @param layout    The specified {@link WindowLayout}.
+         * @param activity  The {@link ActivityRecord} currently being positioned.
+         * @param options   The {@link ActivityOptions} specified for the activity.
+         * @param current   The current bounds. This can differ from the initial bounds as it
+         *                  represents the modified bounds up to this point.
+         * @param result    The {@link Rect} which the positioner should return its modified bounds.
+         *                  Any merging of the current bounds should be already applied to this
+         *                  value as well before returning.
+         * @return A {@link Result} representing the result of the bounds calculation.
+         */
+        @Result
+        int onCalculateBounds(TaskRecord task, WindowLayout layout, ActivityRecord activity,
+                ActivityOptions options, Rect current, Rect result);
+    }
+}
diff --git a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
index 0dc73e9..6389075 100644
--- a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
+++ b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
@@ -19,7 +19,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
-import android.annotation.Nullable;
+import android.app.ActivityOptions;
 import android.content.pm.ActivityInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -36,8 +36,10 @@
  * and compares corners of the task with corners of existing tasks. If some two pairs of corners are
  * sufficiently close enough, it shifts the bounds of the new task and tries again. When it exhausts
  * all possible shifts, it gives up and puts the task in the original position.
+ *
+ * Note that the only gravities of concern are the corners and the center.
  */
-class LaunchingTaskPositioner {
+class LaunchingTaskPositioner implements LaunchingBoundsController.LaunchingBoundsPositioner {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "LaunchingTaskPositioner" : TAG_AM;
 
     // Determines how close window frames/corners have to be to call them colliding.
@@ -74,44 +76,50 @@
      * Tries to set task's bound in a way that it won't collide with any other task. By colliding
      * we mean that two tasks have left-top corner very close to each other, so one might get
      * obfuscated by the other one.
-     *
-     * @param task Task for which we want to find bounds that won't collide with other.
-     * @param tasks Existing tasks with which we don't want to collide.
-     * @param windowLayout Optional information from the client about how it would like to be sized
-     *                      and positioned.
      */
-    void updateDefaultBounds(TaskRecord task, ArrayList<TaskRecord> tasks,
-            @Nullable ActivityInfo.WindowLayout windowLayout) {
+    @Override
+    public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout,
+            ActivityRecord activity, ActivityOptions options, Rect current, Rect result) {
+        // We can only apply positioning if we're in a freeform stack.
+        if (task == null || task.getStack() == null || !task.inFreeformWindowingMode()) {
+            return RESULT_SKIP;
+        }
+
+        final ArrayList<TaskRecord> tasks = task.getStack().getAllTasks();
+
         updateAvailableRect(task, mAvailableRect);
 
-        if (windowLayout == null) {
-            positionCenter(task, tasks, mAvailableRect, getFreeformWidth(mAvailableRect),
-                    getFreeformHeight(mAvailableRect));
-            return;
+        if (layout == null) {
+            positionCenter(tasks, mAvailableRect, getFreeformWidth(mAvailableRect),
+                    getFreeformHeight(mAvailableRect), result);
+            return RESULT_CONTINUE;
         }
-        int width = getFinalWidth(windowLayout, mAvailableRect);
-        int height = getFinalHeight(windowLayout, mAvailableRect);
-        int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
-        int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+
+        int width = getFinalWidth(layout, mAvailableRect);
+        int height = getFinalHeight(layout, mAvailableRect);
+        int verticalGravity = layout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+        int horizontalGravity = layout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
         if (verticalGravity == Gravity.TOP) {
             if (horizontalGravity == Gravity.RIGHT) {
-                positionTopRight(task, tasks, mAvailableRect, width, height);
+                positionTopRight(tasks, mAvailableRect, width, height, result);
             } else {
-                positionTopLeft(task, tasks, mAvailableRect, width, height);
+                positionTopLeft(tasks, mAvailableRect, width, height, result);
             }
         } else if (verticalGravity == Gravity.BOTTOM) {
             if (horizontalGravity == Gravity.RIGHT) {
-                positionBottomRight(task, tasks, mAvailableRect, width, height);
+                positionBottomRight(tasks, mAvailableRect, width, height, result);
             } else {
-                positionBottomLeft(task, tasks, mAvailableRect, width, height);
+                positionBottomLeft(tasks, mAvailableRect, width, height, result);
             }
         } else {
             // Some fancy gravity setting that we don't support yet. We just put the activity in the
             // center.
-            Slog.w(TAG, "Received unsupported gravity: " + windowLayout.gravity
+            Slog.w(TAG, "Received unsupported gravity: " + layout.gravity
                     + ", positioning in the center instead.");
-            positionCenter(task, tasks, mAvailableRect, width, height);
+            positionCenter(tasks, mAvailableRect, width, height, result);
         }
+
+        return RESULT_CONTINUE;
     }
 
     private void updateAvailableRect(TaskRecord task, Rect availableRect) {
@@ -179,50 +187,50 @@
         return height;
     }
 
-    private void positionBottomLeft(TaskRecord task, ArrayList<TaskRecord> tasks,
-            Rect availableRect, int width, int height) {
+    private void positionBottomLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
         mTmpProposal.set(availableRect.left, availableRect.bottom - height,
                 availableRect.left + width, availableRect.bottom);
-        position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
-                SHIFT_POLICY_HORIZONTAL_RIGHT);
+        position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT,
+                result);
     }
 
-    private void positionBottomRight(TaskRecord task, ArrayList<TaskRecord> tasks,
-            Rect availableRect, int width, int height) {
+    private void positionBottomRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
         mTmpProposal.set(availableRect.right - width, availableRect.bottom - height,
                 availableRect.right, availableRect.bottom);
-        position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
-                SHIFT_POLICY_HORIZONTAL_LEFT);
+        position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT,
+                result);
     }
 
-    private void positionTopLeft(TaskRecord task, ArrayList<TaskRecord> tasks,
-            Rect availableRect, int width, int height) {
+    private void positionTopLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
         mTmpProposal.set(availableRect.left, availableRect.top,
                 availableRect.left + width, availableRect.top + height);
-        position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
-                SHIFT_POLICY_HORIZONTAL_RIGHT);
+        position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT,
+                result);
     }
 
-    private void positionTopRight(TaskRecord task, ArrayList<TaskRecord> tasks,
-            Rect availableRect, int width, int height) {
+    private void positionTopRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
         mTmpProposal.set(availableRect.right - width, availableRect.top,
                 availableRect.right, availableRect.top + height);
-        position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
-                SHIFT_POLICY_HORIZONTAL_LEFT);
+        position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT,
+                result);
     }
 
-    private void positionCenter(TaskRecord task, ArrayList<TaskRecord> tasks,
-            Rect availableRect, int width, int height) {
+    private void positionCenter(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
         final int defaultFreeformLeft = getFreeformStartLeft(availableRect);
         final int defaultFreeformTop = getFreeformStartTop(availableRect);
         mTmpProposal.set(defaultFreeformLeft, defaultFreeformTop,
                 defaultFreeformLeft + width, defaultFreeformTop + height);
-        position(task, tasks, availableRect, mTmpProposal, ALLOW_RESTART,
-                SHIFT_POLICY_DIAGONAL_DOWN);
+        position(tasks, availableRect, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN,
+                result);
     }
 
-    private void position(TaskRecord task, ArrayList<TaskRecord> tasks, Rect availableRect,
-            Rect proposal, boolean allowRestart, int shiftPolicy) {
+    private void position(ArrayList<TaskRecord> tasks, Rect availableRect,
+            Rect proposal, boolean allowRestart, int shiftPolicy, Rect result) {
         mTmpOriginal.set(proposal);
         boolean restarted = false;
         while (boundsConflict(proposal, tasks)) {
@@ -252,7 +260,7 @@
                 break;
             }
         }
-        task.updateOverrideConfiguration(proposal);
+        result.set(proposal);
     }
 
     private boolean shiftedTooFar(Rect start, Rect availableRect, int shiftPolicy) {
diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java
index 1c094c1..11daf3f 100644
--- a/services/core/java/com/android/server/am/LockTaskController.java
+++ b/services/core/java/com/android/server/am/LockTaskController.java
@@ -430,7 +430,7 @@
         }
 
         if (andResume) {
-            mSupervisor.findTaskToMoveToFrontLocked(task, 0, null, reason,
+            mSupervisor.findTaskToMoveToFront(task, 0, null, reason,
                     lockTaskModeState != LOCK_TASK_MODE_NONE);
             mSupervisor.resumeFocusedStackTopActivityLocked();
             mWindowManager.executeAppTransition();
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 4080c27..f04bdb3 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -30,7 +30,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
+import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
@@ -74,7 +76,6 @@
 import static com.android.server.am.proto.TaskRecordProto.ORIG_ACTIVITY;
 import static com.android.server.am.proto.TaskRecordProto.REAL_ACTIVITY;
 import static com.android.server.am.proto.TaskRecordProto.RESIZE_MODE;
-import static com.android.server.am.proto.TaskRecordProto.RETURN_TO_TYPE;
 import static com.android.server.am.proto.TaskRecordProto.STACK_ID;
 import static com.android.server.am.proto.TaskRecordProto.ACTIVITY_TYPE;
 
@@ -172,7 +173,6 @@
     // Current version of the task record we persist. Used to check if we need to run any upgrade
     // code.
     private static final int PERSIST_TASK_VERSION = 1;
-    private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
 
     static final int INVALID_TASK_ID = -1;
     private static final int INVALID_MIN_SIZE = -1;
@@ -187,13 +187,13 @@
             REPARENT_KEEP_STACK_AT_FRONT,
             REPARENT_LEAVE_STACK_IN_PLACE
     })
-    public @interface ReparentMoveStackMode {}
+    @interface ReparentMoveStackMode {}
     // Moves the stack to the front if it was not at the front
-    public static final int REPARENT_MOVE_STACK_TO_FRONT = 0;
+    static final int REPARENT_MOVE_STACK_TO_FRONT = 0;
     // Only moves the stack to the front if it was focused or front most already
-    public static final int REPARENT_KEEP_STACK_AT_FRONT = 1;
+    static final int REPARENT_KEEP_STACK_AT_FRONT = 1;
     // Do not move the stack as a part of reparenting
-    public static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
+    static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
 
     final int taskId;       // Unique identifier for this task.
     String affinity;        // The affinity name for this task, or null; may change identity.
@@ -265,12 +265,6 @@
      * (positive) or back (negative). Absolute value indicates time. */
     long mLastTimeMoved = System.currentTimeMillis();
 
-    /** Indication of what to run next when task exits. */
-    // TODO: Shouldn't be needed if we have things in visual order. I.e. we stop using stacks or
-    // have a stack per standard application type...
-    /*@WindowConfiguration.ActivityType*/
-    private int mTaskToReturnTo = ACTIVITY_TYPE_STANDARD;
-
     /** If original intent did not allow relinquishing task identity, save that information */
     private boolean mNeverRelinquishIdentity = true;
 
@@ -278,7 +272,6 @@
     // do not want to delete the stack when the task goes empty.
     private boolean mReuseTask = false;
 
-    private final String mFilename;
     CharSequence lastDescription; // Last description captured for this item.
 
     int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
@@ -324,8 +317,6 @@
     TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
             IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
         mService = service;
-        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
-                TaskPersister.IMAGE_EXTENSION;
         userId = UserHandle.getUserId(info.applicationInfo.uid);
         taskId = _taskId;
         lastActiveTime = SystemClock.elapsedRealtime();
@@ -345,8 +336,6 @@
     TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
             TaskDescription _taskDescription) {
         mService = service;
-        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
-                TaskPersister.IMAGE_EXTENSION;
         userId = UserHandle.getUserId(info.applicationInfo.uid);
         taskId = _taskId;
         lastActiveTime = SystemClock.elapsedRealtime();
@@ -365,7 +354,6 @@
         maxRecents = Math.min(Math.max(info.maxRecents, 1),
                 ActivityManager.getMaxAppRecentsLimitStatic());
 
-        mTaskToReturnTo = ACTIVITY_TYPE_HOME;
         lastTaskDescription = _taskDescription;
         touchActiveTime();
         mService.mTaskChangeNotificationController.notifyTaskCreated(_taskId, realActivity);
@@ -382,8 +370,6 @@
             int resizeMode, boolean supportsPictureInPicture, boolean _realActivitySuspended,
             boolean userSetupComplete, int minWidth, int minHeight) {
         mService = service;
-        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
-                TaskPersister.IMAGE_EXTENSION;
         taskId = _taskId;
         intent = _intent;
         affinityIntent = _affinityIntent;
@@ -398,7 +384,6 @@
         isAvailable = true;
         autoRemoveRecents = _autoRemoveRecents;
         askedCompatMode = _askedCompatMode;
-        mTaskToReturnTo = ACTIVITY_TYPE_HOME;
         userId = _userId;
         mUserSetupComplete = userSetupComplete;
         effectiveUid = _effectiveUid;
@@ -704,7 +689,7 @@
             } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) {
                 Rect bounds = getLaunchBounds();
                 if (bounds == null) {
-                    toStack.layoutTaskInStack(this, null);
+                    mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null);
                     bounds = mBounds;
                 }
                 kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
@@ -901,29 +886,9 @@
         return this.intent.filterEquals(intent);
     }
 
-    void setTaskToReturnTo(/*@WindowConfiguration.ActivityType*/ int taskToReturnTo) {
-        mTaskToReturnTo = taskToReturnTo == ACTIVITY_TYPE_RECENTS
-                ? ACTIVITY_TYPE_HOME : taskToReturnTo;
-    }
-
-    void setTaskToReturnTo(ActivityRecord source) {
-        if (source.isActivityTypeRecents()) {
-            setTaskToReturnTo(ACTIVITY_TYPE_RECENTS);
-        } else if (source.isActivityTypeAssistant()) {
-            setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
-        }
-    }
-
-    int getTaskToReturnTo() {
-        return mTaskToReturnTo;
-    }
-
-    boolean returnsToHomeTask() {
-        return mTaskToReturnTo == ACTIVITY_TYPE_HOME;
-    }
-
-    boolean returnsToStandardTask() {
-        return mTaskToReturnTo == ACTIVITY_TYPE_STANDARD;
+    boolean returnsToHomeStack() {
+        final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME;
+        return (intent.getFlags() & returnHomeFlags) == returnHomeFlags;
     }
 
     void setPrevAffiliate(TaskRecord prevAffiliate) {
@@ -1444,14 +1409,6 @@
                 " mLockTaskAuth=" + lockTaskAuthToString());
     }
 
-    boolean isOverHomeStack() {
-        return mTaskToReturnTo == ACTIVITY_TYPE_HOME;
-    }
-
-    boolean isOverAssistantStack() {
-        return mTaskToReturnTo == ACTIVITY_TYPE_ASSISTANT;
-    }
-
     private boolean isResizeable(boolean checkSupportsPip) {
         return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode)
                 || (checkSupportsPip && mSupportsPictureInPicture));
@@ -2086,7 +2043,7 @@
             if (mLastNonFullscreenBounds != null) {
                 updateOverrideConfiguration(mLastNonFullscreenBounds);
             } else {
-                inStack.layoutTaskInStack(this, null);
+                mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null);
             }
         } else {
             updateOverrideConfiguration(inStack.mBounds);
@@ -2161,13 +2118,11 @@
             pw.print(prefix); pw.print("realActivity=");
             pw.println(realActivity.flattenToShortString());
         }
-        if (autoRemoveRecents || isPersistable || !isActivityTypeStandard()
-                || mTaskToReturnTo != ACTIVITY_TYPE_STANDARD || numFullscreen != 0) {
+        if (autoRemoveRecents || isPersistable || !isActivityTypeStandard() || numFullscreen != 0) {
             pw.print(prefix); pw.print("autoRemoveRecents="); pw.print(autoRemoveRecents);
                     pw.print(" isPersistable="); pw.print(isPersistable);
                     pw.print(" numFullscreen="); pw.print(numFullscreen);
-                    pw.print(" activityType="); pw.print(getActivityType());
-                    pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo);
+                    pw.print(" activityType="); pw.println(getActivityType());
         }
         if (rootWasReset || mNeverRelinquishIdentity || mReuseTask
                 || mLockTaskAuth != LOCK_TASK_AUTH_PINNABLE) {
@@ -2267,7 +2222,6 @@
             proto.write(ORIG_ACTIVITY, origActivity.flattenToShortString());
         }
         proto.write(ACTIVITY_TYPE, getActivityType());
-        proto.write(RETURN_TO_TYPE, mTaskToReturnTo);
         proto.write(RESIZE_MODE, mResizeMode);
         proto.write(FULLSCREEN, mFullscreen);
         if (mBounds != null) {
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index d7cd81f..59870cb 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -28,6 +28,7 @@
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_UNSPECIFIED;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
 import static com.android.server.ConnectivityService.SHORT_ARG;
 
 import android.app.Notification;
@@ -60,6 +61,7 @@
 import android.net.RouteInfo;
 import android.net.util.PrefixUtils;
 import android.net.util.SharedLog;
+import android.net.util.VersionedBroadcastListener;
 import android.net.wifi.WifiManager;
 import android.os.Binder;
 import android.os.Bundle;
@@ -68,6 +70,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
@@ -184,6 +187,8 @@
     // TODO: Figure out how to merge this and other downstream-tracking objects
     // into a single coherent structure.
     private final HashSet<TetherInterfaceStateMachine> mForwardedDownstreams;
+    private final VersionedBroadcastListener mCarrierConfigChange;
+    // TODO: Delete SimChangeListener; it's obsolete.
     private final SimChangeListener mSimChange;
 
     private volatile TetheringConfiguration mConfig;
@@ -224,11 +229,26 @@
         mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
                 mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
         mForwardedDownstreams = new HashSet<>();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
+        mCarrierConfigChange = new VersionedBroadcastListener(
+                "CarrierConfigChangeListener", mContext, smHandler, filter,
+                (Intent ignored) -> {
+                    mLog.log("OBSERVED carrier config change");
+                    reevaluateSimCardProvisioning();
+                });
+        // TODO: Remove SimChangeListener altogether. For now, we retain it
+        // for logging purposes in case we need to debug something that might
+        // be related to changing signals from ACTION_SIM_STATE_CHANGED to
+        // ACTION_CARRIER_CONFIG_CHANGED.
         mSimChange = new SimChangeListener(
-                mContext, smHandler, () -> reevaluateSimCardProvisioning());
+                mContext, smHandler, () -> {
+                    mLog.log("OBSERVED SIM card change");
+                });
 
         mStateReceiver = new StateReceiver();
-        IntentFilter filter = new IntentFilter();
+        filter = new IntentFilter();
         filter.addAction(UsbManager.ACTION_USB_STATE);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
@@ -364,18 +384,30 @@
             return false;
         }
 
+        if (carrierConfigAffirmsEntitlementCheckNotRequired()) {
+            return false;
+        }
+        return (provisionApp.length == 2);
+    }
+
+    // The logic here is aimed solely at confirming that a CarrierConfig exists
+    // and affirms that entitlement checks are not required.
+    //
+    // TODO: find a better way to express this, or alter the checking process
+    // entirely so that this is more intuitive.
+    private boolean carrierConfigAffirmsEntitlementCheckNotRequired() {
         // Check carrier config for entitlement checks
         final CarrierConfigManager configManager = (CarrierConfigManager) mContext
              .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager != null && configManager.getConfig() != null) {
-            // we do have a CarrierConfigManager and it has a config.
-            boolean isEntitlementCheckRequired = configManager.getConfig().getBoolean(
-                    CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
-            if (!isEntitlementCheckRequired) {
-                return false;
-            }
-        }
-        return (provisionApp.length == 2);
+        if (configManager == null) return false;
+
+        final PersistableBundle carrierConfig = configManager.getConfig();
+        if (carrierConfig == null) return false;
+
+        // A CarrierConfigManager was found and it has a config.
+        final boolean isEntitlementCheckRequired = carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
+        return !isEntitlementCheckRequired;
     }
 
     // Used by the SIM card change observation code.
@@ -818,6 +850,7 @@
             } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
                 handleWifiApAction(intent);
             } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+                mLog.log("OBSERVED configuration changed");
                 updateConfiguration();
             }
         }
@@ -1192,6 +1225,7 @@
 
     private void reevaluateSimCardProvisioning() {
         if (!hasMobileHotspotProvisionApp()) return;
+        if (carrierConfigAffirmsEntitlementCheckNotRequired()) return;
 
         ArrayList<Integer> tethered = new ArrayList<>();
         synchronized (mPublicSync) {
@@ -1559,6 +1593,7 @@
                     return;
                 }
 
+                mCarrierConfigChange.startListening();
                 mSimChange.startListening();
                 mUpstreamNetworkMonitor.start();
 
@@ -1576,6 +1611,7 @@
                 mOffload.stop();
                 mUpstreamNetworkMonitor.stop();
                 mSimChange.stopListening();
+                mCarrierConfigChange.stopListening();
                 notifyDownstreamsOfNewUpstreamIface(null);
                 handleNewUpstreamNetworkState(null);
             }
diff --git a/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java b/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
index 3e60f9f..33c9355 100644
--- a/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
+++ b/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
@@ -23,12 +23,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.util.VersionedBroadcastListener;
+import android.net.util.VersionedBroadcastListener.IntentCallback;
 import android.os.Handler;
 import android.util.Log;
 
 import com.android.internal.telephony.TelephonyIntents;
 
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 
 
 /**
@@ -37,88 +40,40 @@
  *
  * @hide
  */
-public class SimChangeListener {
+public class SimChangeListener extends VersionedBroadcastListener {
     private static final String TAG = SimChangeListener.class.getSimpleName();
     private static final boolean DBG = false;
 
-    private final Context mContext;
-    private final Handler mTarget;
-    private final AtomicInteger mSimBcastGenerationNumber;
-    private final Runnable mCallback;
-    private BroadcastReceiver mBroadcastReceiver;
-
     public SimChangeListener(Context ctx, Handler handler, Runnable onSimCardLoadedCallback) {
-        mContext = ctx;
-        mTarget = handler;
-        mCallback = onSimCardLoadedCallback;
-        mSimBcastGenerationNumber = new AtomicInteger(0);
+        super(TAG, ctx, handler, makeIntentFilter(), makeCallback(onSimCardLoadedCallback));
     }
 
-    public int generationNumber() {
-        return mSimBcastGenerationNumber.get();
-    }
-
-    public void startListening() {
-        if (DBG) Log.d(TAG, "startListening for SIM changes");
-
-        if (mBroadcastReceiver != null) return;
-
-        mBroadcastReceiver = new SimChangeBroadcastReceiver(
-                mSimBcastGenerationNumber.incrementAndGet());
+    private static IntentFilter makeIntentFilter() {
         final IntentFilter filter = new IntentFilter();
         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
-
-        mContext.registerReceiver(mBroadcastReceiver, filter, null, mTarget);
+        return filter;
     }
 
-    public void stopListening() {
-        if (DBG) Log.d(TAG, "stopListening for SIM changes");
+    private static Consumer<Intent> makeCallback(Runnable onSimCardLoadedCallback) {
+        return new Consumer<Intent>() {
+            private boolean mSimNotLoadedSeen = false;
 
-        if (mBroadcastReceiver == null) return;
+            @Override
+            public void accept(Intent intent) {
+                final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE);
+                Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
+                        mSimNotLoadedSeen);
 
-        mSimBcastGenerationNumber.incrementAndGet();
-        mContext.unregisterReceiver(mBroadcastReceiver);
-        mBroadcastReceiver = null;
-    }
+                if (!INTENT_VALUE_ICC_LOADED.equals(state)) {
+                    mSimNotLoadedSeen = true;
+                    return;
+                }
 
-    private boolean isSimCardLoaded(String state) {
-        return INTENT_VALUE_ICC_LOADED.equals(state);
-    }
-
-    private class SimChangeBroadcastReceiver extends BroadcastReceiver {
-        // used to verify this receiver is still current
-        final private int mGenerationNumber;
-
-        // used to check the sim state transition from non-loaded to loaded
-        private boolean mSimNotLoadedSeen = false;
-
-        public SimChangeBroadcastReceiver(int generationNumber) {
-            mGenerationNumber = generationNumber;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final int currentGenerationNumber = mSimBcastGenerationNumber.get();
-
-            if (DBG) {
-                Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
-                        ", current generationNumber=" + currentGenerationNumber);
+                if (mSimNotLoadedSeen) {
+                    mSimNotLoadedSeen = false;
+                    onSimCardLoadedCallback.run();
+                }
             }
-            if (mGenerationNumber != currentGenerationNumber) return;
-
-            final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE);
-            Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
-                    mSimNotLoadedSeen);
-
-            if (!isSimCardLoaded(state)) {
-                mSimNotLoadedSeen = true;
-                return;
-            }
-
-            if (mSimNotLoadedSeen) {
-                mSimNotLoadedSeen = false;
-                mCallback.run();
-            }
-        }
+        };
     }
 }
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index d3fd3a9..ae01c43 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -216,7 +216,7 @@
             final JobInfo ji = job.getJob();
             mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
                     ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
-                    isDeadlineExpired, triggeredUris, triggeredAuthorities);
+                    isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
             mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
 
             // Once we'e begun executing a job, we by definition no longer care whether
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index 78367fe..c928c07 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -125,6 +125,11 @@
         changed |= jobStatus.setUnmeteredConstraintSatisfied(unmetered);
         changed |= jobStatus.setNotRoamingConstraintSatisfied(notRoaming);
 
+        // Pass along the evaluated network for job to use; prevents race
+        // conditions as default routes change over time, and opens the door to
+        // using non-default routes.
+        jobStatus.network = network;
+
         // Track system-uid connected/validated as a general reportable proxy for the
         // overall state of connectivity constraint satisfiability.
         if (jobUid == Process.SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 23caa8c..1a27c0a 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -22,6 +22,7 @@
 import android.app.job.JobWorkItem;
 import android.content.ClipData;
 import android.content.ComponentName;
+import android.net.Network;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -167,6 +168,7 @@
     // These are filled in by controllers when preparing for execution.
     public ArraySet<Uri> changedUris;
     public ArraySet<String> changedAuthorities;
+    public Network network;
 
     public int lastEvaluatedPriority;
 
@@ -1101,6 +1103,9 @@
                 }
             }
         }
+        if (network != null) {
+            pw.print(prefix); pw.print("Network: "); pw.println(network);
+        }
         if (pendingWork != null && pendingWork.size() > 0) {
             pw.print(prefix); pw.println("Pending work:");
             for (int i = 0; i < pendingWork.size(); i++) {
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 6253857..03f662a 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -310,7 +310,7 @@
                 collectingInstaller, mPackageManagerService.mInstallLock, mContext);
 
         String[] libraryDependencies = pkg.usesLibraryFiles;
-        if (pkg.isSystemApp()) {
+        if (pkg.isSystem()) {
             // For system apps, we want to avoid classpaths checks.
             libraryDependencies = NO_LIBRARIES;
         }
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index cf0ffbb..e7b4abb 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -110,7 +110,7 @@
         }
 
         // We do not dexopt a priv-app package when pm.dexopt.priv-apps is false.
-        if (pkg.isPrivilegedApp()) {
+        if (pkg.isPrivileged()) {
             return SystemProperties.getBoolean("pm.dexopt.priv-apps", true);
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7fb2ec4..acec2cb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -397,7 +397,7 @@
     static final boolean DEBUG_UPGRADE = false;
     static final boolean DEBUG_DOMAIN_VERIFICATION = false;
     private static final boolean DEBUG_BACKUP = false;
-    private static final boolean DEBUG_INSTALL = false;
+    public static final boolean DEBUG_INSTALL = false;
     public static final boolean DEBUG_REMOVE = false;
     private static final boolean DEBUG_BROADCASTS = false;
     private static final boolean DEBUG_SHOW_INFO = false;
@@ -522,7 +522,7 @@
      */
     private static final int DEFAULT_VERIFICATION_RESPONSE = PackageManager.VERIFICATION_ALLOW;
 
-    static final String PLATFORM_PACKAGE_NAME = "android";
+    public static final String PLATFORM_PACKAGE_NAME = "android";
 
     static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
 
@@ -542,18 +542,6 @@
 
     private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
 
-    /** Permission grant: not grant the permission. */
-    private static final int GRANT_DENIED = 1;
-
-    /** Permission grant: grant the permission as an install permission. */
-    private static final int GRANT_INSTALL = 2;
-
-    /** Permission grant: grant the permission as a runtime one. */
-    private static final int GRANT_RUNTIME = 3;
-
-    /** Permission grant: grant as runtime a permission that was granted as an install time one. */
-    private static final int GRANT_UPGRADE = 4;
-
     /** Canonical intent used to identify what counts as a "web browser" app */
     private static final Intent sBrowserIntent;
     static {
@@ -1009,8 +997,6 @@
 
     private File mCacheDir;
 
-    private ArraySet<String> mPrivappPermissionsViolations;
-
     private Future<?> mPrepareAppDataFuture;
 
     private static class IFVerificationParams {
@@ -1398,8 +1384,6 @@
     final @NonNull String mServicesSystemSharedLibraryPackageName;
     final @NonNull String mSharedSystemSharedLibraryPackageName;
 
-    final boolean mPermissionReviewRequired;
-
     private final PackageUsage mPackageUsage = new PackageUsage();
     private final CompilerStats mCompilerStats = new CompilerStats();
 
@@ -1921,9 +1905,11 @@
             }
         }
         @Override
-        public void onPermissionUpdated(int userId) {
+        public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
             synchronized (mPackages) {
-                mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+                for (int userId : updatedUserIds) {
+                    mSettings.writeRuntimePermissionsForUserLPr(userId, sync);
+                }
             }
         }
         @Override
@@ -2356,9 +2342,6 @@
 
         mContext = context;
 
-        mPermissionReviewRequired = context.getResources().getBoolean(
-                R.bool.config_permissionReviewRequired);
-
         mFactoryTest = factoryTest;
         mOnlyCore = onlyCore;
         mMetrics = new DisplayMetrics();
@@ -2863,13 +2846,14 @@
             // cases get permissions that the user didn't initially explicitly
             // allow...  it would be nice to have some better way to handle
             // this situation.
-            int updateFlags = UPDATE_PERMISSIONS_ALL;
-            if (ver.sdkVersion != mSdkVersion) {
+            final boolean sdkUpdated = (ver.sdkVersion != mSdkVersion);
+            if (sdkUpdated) {
                 Slog.i(TAG, "Platform changed from " + ver.sdkVersion + " to "
                         + mSdkVersion + "; regranting permissions for internal storage");
-                updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
             }
-            updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);
+            mPermissionManager.updateAllPermissions(
+                    StorageManager.UUID_PRIVATE_INTERNAL, sdkUpdated, mPackages.values(),
+                    mPermissionCallback);
             ver.sdkVersion = mSdkVersion;
 
             // If this is the first boot or an update from pre-M, and it is a normal
@@ -3588,7 +3572,7 @@
         for (String packageName : packages) {
             PackageParser.Package pkg = mPackages.get(packageName);
             if (pkg != null) {
-                if (!pkg.isSystemApp()) {
+                if (!pkg.isSystem()) {
                     Slog.w(TAG, "Non-system app '" + packageName + "' in sysconfig <app-link>");
                     continue;
                 }
@@ -5269,7 +5253,9 @@
         }
 
         synchronized (mPackages) {
-            updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL);
+            mPermissionManager.updateAllPermissions(
+                    StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(),
+                    mPermissionCallback);
             for (int userId : UserManagerService.getInstance().getUserIds()) {
                 final int packageCount = mPackages.size();
                 for (int i = 0; i < packageCount; i++) {
@@ -5286,7 +5272,8 @@
 
     @Override
     public int getPermissionFlags(String permName, String packageName, int userId) {
-        return mPermissionManager.getPermissionFlags(permName, packageName, getCallingUid(), userId);
+        return mPermissionManager.getPermissionFlags(
+                permName, packageName, getCallingUid(), userId);
     }
 
     @Override
@@ -9831,7 +9818,7 @@
                 // it is better for the user to reinstall than to be in an limbo
                 // state. Also libs disappearing under an app should never happen
                 // - just in case.
-                if (!pkg.isSystemApp() || pkg.isUpdatedSystemApp()) {
+                if (!pkg.isSystem() || pkg.isUpdatedSystemApp()) {
                     final int flags = pkg.isUpdatedSystemApp()
                             ? PackageManager.DELETE_KEEP_DATA : 0;
                     deletePackageLIF(pkg.packageName, null, true, sUserManager.getUserIds(),
@@ -9980,7 +9967,7 @@
         assertPackageIsValid(pkg, policyFlags, scanFlags);
 
         if (Build.IS_DEBUGGABLE &&
-                pkg.isPrivilegedApp() &&
+                pkg.isPrivileged() &&
                 !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
             PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg);
         }
@@ -11873,611 +11860,6 @@
         }
     }
 
-    public static final int UPDATE_PERMISSIONS_ALL = 1<<0;
-    public static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
-    public static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
-
-    private void updatePermissionsLPw(PackageParser.Package pkg, int flags) {
-        // Update the parent permissions
-        updatePermissionsLPw(pkg.packageName, pkg, flags);
-        // Update the child permissions
-        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
-        for (int i = 0; i < childCount; i++) {
-            PackageParser.Package childPkg = pkg.childPackages.get(i);
-            updatePermissionsLPw(childPkg.packageName, childPkg, flags);
-        }
-    }
-
-    private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo,
-            int flags) {
-        final String volumeUuid = (pkgInfo != null) ? getVolumeUuidForPackage(pkgInfo) : null;
-        updatePermissionsLocked(changingPkg, pkgInfo, volumeUuid, flags);
-    }
-
-    private void updatePermissionsLocked(String changingPkg,
-            PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) {
-        // TODO: Most of the methods exposing BasePermission internals [source package name,
-        // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't
-        // have package settings, we should make note of it elsewhere [map between
-        // source package name and BasePermission] and cycle through that here. Then we
-        // define a single method on BasePermission that takes a PackageSetting, changing
-        // package name and a package.
-        // NOTE: With this approach, we also don't need to tree trees differently than
-        // normal permissions. Today, we need two separate loops because these BasePermission
-        // objects are stored separately.
-        // Make sure there are no dangling permission trees.
-        flags = mPermissionManager.updatePermissionTrees(changingPkg, pkgInfo, flags);
-
-        // Make sure all dynamic permissions have been assigned to a package,
-        // and make sure there are no dangling permissions.
-        flags = mPermissionManager.updatePermissions(changingPkg, pkgInfo, flags);
-
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions");
-        // Now update the permissions for all packages, in particular
-        // replace the granted permissions of the system packages.
-        if ((flags&UPDATE_PERMISSIONS_ALL) != 0) {
-            for (PackageParser.Package pkg : mPackages.values()) {
-                if (pkg != pkgInfo) {
-                    // Only replace for packages on requested volume
-                    final String volumeUuid = getVolumeUuidForPackage(pkg);
-                    final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0)
-                            && Objects.equals(replaceVolumeUuid, volumeUuid);
-                    grantPermissionsLPw(pkg, replace, changingPkg);
-                }
-            }
-        }
-
-        if (pkgInfo != null) {
-            // Only replace for packages on requested volume
-            final String volumeUuid = getVolumeUuidForPackage(pkgInfo);
-            final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
-                    && Objects.equals(replaceVolumeUuid, volumeUuid);
-            grantPermissionsLPw(pkgInfo, replace, changingPkg);
-        }
-        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-    }
-
-    private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace,
-            String packageOfInterest) {
-        // IMPORTANT: There are two types of permissions: install and runtime.
-        // Install time permissions are granted when the app is installed to
-        // all device users and users added in the future. Runtime permissions
-        // are granted at runtime explicitly to specific users. Normal and signature
-        // protected permissions are install time permissions. Dangerous permissions
-        // are install permissions if the app's target SDK is Lollipop MR1 or older,
-        // otherwise they are runtime permissions. This function does not manage
-        // runtime permissions except for the case an app targeting Lollipop MR1
-        // being upgraded to target a newer SDK, in which case dangerous permissions
-        // are transformed from install time to runtime ones.
-
-        final PackageSetting ps = (PackageSetting) pkg.mExtras;
-        if (ps == null) {
-            return;
-        }
-
-        PermissionsState permissionsState = ps.getPermissionsState();
-        PermissionsState origPermissions = permissionsState;
-
-        final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
-
-        boolean runtimePermissionsRevoked = false;
-        int[] changedRuntimePermissionUserIds = EMPTY_INT_ARRAY;
-
-        boolean changedInstallPermission = false;
-
-        if (replace) {
-            ps.installPermissionsFixed = false;
-            if (!ps.isSharedUser()) {
-                origPermissions = new PermissionsState(permissionsState);
-                permissionsState.reset();
-            } else {
-                // We need to know only about runtime permission changes since the
-                // calling code always writes the install permissions state but
-                // the runtime ones are written only if changed. The only cases of
-                // changed runtime permissions here are promotion of an install to
-                // runtime and revocation of a runtime from a shared user.
-                changedRuntimePermissionUserIds =
-                        mPermissionManager.revokeUnusedSharedUserPermissions(
-                                ps.sharedUser, UserManagerService.getInstance().getUserIds());
-                if (!ArrayUtils.isEmpty(changedRuntimePermissionUserIds)) {
-                    runtimePermissionsRevoked = true;
-                }
-            }
-        }
-
-        permissionsState.setGlobalGids(mPermissionManager.getGlobalGidsTEMP());
-
-        final int N = pkg.requestedPermissions.size();
-        for (int i=0; i<N; i++) {
-            final String name = pkg.requestedPermissions.get(i);
-            final BasePermission bp = (BasePermission) mPermissionManager.getPermissionTEMP(name);
-            final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
-                    >= Build.VERSION_CODES.M;
-
-            if (DEBUG_INSTALL) {
-                Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp);
-            }
-
-            if (bp == null || bp.getSourcePackageSetting() == null) {
-                if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
-                    if (DEBUG_PERMISSIONS) {
-                        Slog.i(TAG, "Unknown permission " + name
-                                + " in package " + pkg.packageName);
-                    }
-                }
-                continue;
-            }
-
-
-            // Limit ephemeral apps to ephemeral allowed permissions.
-            if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
-                if (DEBUG_PERMISSIONS) {
-                    Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() + " for package "
-                            + pkg.packageName);
-                }
-                continue;
-            }
-
-            if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
-                if (DEBUG_PERMISSIONS) {
-                    Log.i(TAG, "Denying runtime-only permission " + bp.getName() + " for package "
-                            + pkg.packageName);
-                }
-                continue;
-            }
-
-            final String perm = bp.getName();
-            boolean allowedSig = false;
-            int grant = GRANT_DENIED;
-
-            // Keep track of app op permissions.
-            if (bp.isAppOp()) {
-                mSettings.addAppOpPackage(perm, pkg.packageName);
-            }
-
-            if (bp.isNormal()) {
-                // For all apps normal permissions are install time ones.
-                grant = GRANT_INSTALL;
-            } else if (bp.isRuntime()) {
-                // If a permission review is required for legacy apps we represent
-                // their permissions as always granted runtime ones since we need
-                // to keep the review required permission flag per user while an
-                // install permission's state is shared across all users.
-                if (!appSupportsRuntimePermissions && !mPermissionReviewRequired) {
-                    // For legacy apps dangerous permissions are install time ones.
-                    grant = GRANT_INSTALL;
-                } else if (origPermissions.hasInstallPermission(bp.getName())) {
-                    // For legacy apps that became modern, install becomes runtime.
-                    grant = GRANT_UPGRADE;
-                } else if (mPromoteSystemApps
-                        && isSystemApp(ps)
-                        && mExistingSystemPackages.contains(ps.name)) {
-                    // For legacy system apps, install becomes runtime.
-                    // We cannot check hasInstallPermission() for system apps since those
-                    // permissions were granted implicitly and not persisted pre-M.
-                    grant = GRANT_UPGRADE;
-                } else {
-                    // For modern apps keep runtime permissions unchanged.
-                    grant = GRANT_RUNTIME;
-                }
-            } else if (bp.isSignature()) {
-                // For all apps signature permissions are install time ones.
-                allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
-                if (allowedSig) {
-                    grant = GRANT_INSTALL;
-                }
-            }
-
-            if (DEBUG_PERMISSIONS) {
-                Slog.i(TAG, "Granting permission " + perm + " to package " + pkg.packageName);
-            }
-
-            if (grant != GRANT_DENIED) {
-                if (!isSystemApp(ps) && ps.installPermissionsFixed) {
-                    // If this is an existing, non-system package, then
-                    // we can't add any new permissions to it.
-                    if (!allowedSig && !origPermissions.hasInstallPermission(perm)) {
-                        // Except...  if this is a permission that was added
-                        // to the platform (note: need to only do this when
-                        // updating the platform).
-                        if (!isNewPlatformPermissionForPackage(perm, pkg)) {
-                            grant = GRANT_DENIED;
-                        }
-                    }
-                }
-
-                switch (grant) {
-                    case GRANT_INSTALL: {
-                        // Revoke this as runtime permission to handle the case of
-                        // a runtime permission being downgraded to an install one.
-                        // Also in permission review mode we keep dangerous permissions
-                        // for legacy apps
-                        for (int userId : UserManagerService.getInstance().getUserIds()) {
-                            if (origPermissions.getRuntimePermissionState(
-                                    perm, userId) != null) {
-                                // Revoke the runtime permission and clear the flags.
-                                origPermissions.revokeRuntimePermission(bp, userId);
-                                origPermissions.updatePermissionFlags(bp, userId,
-                                      PackageManager.MASK_PERMISSION_FLAGS, 0);
-                                // If we revoked a permission permission, we have to write.
-                                changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                        changedRuntimePermissionUserIds, userId);
-                            }
-                        }
-                        // Grant an install permission.
-                        if (permissionsState.grantInstallPermission(bp) !=
-                                PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                            changedInstallPermission = true;
-                        }
-                    } break;
-
-                    case GRANT_RUNTIME: {
-                        // Grant previously granted runtime permissions.
-                        for (int userId : UserManagerService.getInstance().getUserIds()) {
-                            PermissionState permissionState = origPermissions
-                                    .getRuntimePermissionState(perm, userId);
-                            int flags = permissionState != null
-                                    ? permissionState.getFlags() : 0;
-                            if (origPermissions.hasRuntimePermission(perm, userId)) {
-                                // Don't propagate the permission in a permission review mode if
-                                // the former was revoked, i.e. marked to not propagate on upgrade.
-                                // Note that in a permission review mode install permissions are
-                                // represented as constantly granted runtime ones since we need to
-                                // keep a per user state associated with the permission. Also the
-                                // revoke on upgrade flag is no longer applicable and is reset.
-                                final boolean revokeOnUpgrade = (flags & PackageManager
-                                        .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
-                                if (revokeOnUpgrade) {
-                                    flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
-                                    // Since we changed the flags, we have to write.
-                                    changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                            changedRuntimePermissionUserIds, userId);
-                                }
-                                if (!mPermissionReviewRequired || !revokeOnUpgrade) {
-                                    if (permissionsState.grantRuntimePermission(bp, userId) ==
-                                            PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                                        // If we cannot put the permission as it was,
-                                        // we have to write.
-                                        changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                                changedRuntimePermissionUserIds, userId);
-                                    }
-                                }
-
-                                // If the app supports runtime permissions no need for a review.
-                                if (mPermissionReviewRequired
-                                        && appSupportsRuntimePermissions
-                                        && (flags & PackageManager
-                                                .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
-                                    flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-                                    // Since we changed the flags, we have to write.
-                                    changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                            changedRuntimePermissionUserIds, userId);
-                                }
-                            } else if (mPermissionReviewRequired
-                                    && !appSupportsRuntimePermissions) {
-                                // For legacy apps that need a permission review, every new
-                                // runtime permission is granted but it is pending a review.
-                                // We also need to review only platform defined runtime
-                                // permissions as these are the only ones the platform knows
-                                // how to disable the API to simulate revocation as legacy
-                                // apps don't expect to run with revoked permissions.
-                                if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) {
-                                    if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
-                                        flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
-                                        // We changed the flags, hence have to write.
-                                        changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                                changedRuntimePermissionUserIds, userId);
-                                    }
-                                }
-                                if (permissionsState.grantRuntimePermission(bp, userId)
-                                        != PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                                    // We changed the permission, hence have to write.
-                                    changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                            changedRuntimePermissionUserIds, userId);
-                                }
-                            }
-                            // Propagate the permission flags.
-                            permissionsState.updatePermissionFlags(bp, userId, flags, flags);
-                        }
-                    } break;
-
-                    case GRANT_UPGRADE: {
-                        // Grant runtime permissions for a previously held install permission.
-                        PermissionState permissionState = origPermissions
-                                .getInstallPermissionState(perm);
-                        final int flags = permissionState != null ? permissionState.getFlags() : 0;
-
-                        if (origPermissions.revokeInstallPermission(bp)
-                                != PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                            // We will be transferring the permission flags, so clear them.
-                            origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL,
-                                    PackageManager.MASK_PERMISSION_FLAGS, 0);
-                            changedInstallPermission = true;
-                        }
-
-                        // If the permission is not to be promoted to runtime we ignore it and
-                        // also its other flags as they are not applicable to install permissions.
-                        if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) {
-                            for (int userId : currentUserIds) {
-                                if (permissionsState.grantRuntimePermission(bp, userId) !=
-                                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                                    // Transfer the permission flags.
-                                    permissionsState.updatePermissionFlags(bp, userId,
-                                            flags, flags);
-                                    // If we granted the permission, we have to write.
-                                    changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                            changedRuntimePermissionUserIds, userId);
-                                }
-                            }
-                        }
-                    } break;
-
-                    default: {
-                        if (packageOfInterest == null
-                                || packageOfInterest.equals(pkg.packageName)) {
-                            if (DEBUG_PERMISSIONS) {
-                                Slog.i(TAG, "Not granting permission " + perm
-                                        + " to package " + pkg.packageName
-                                        + " because it was previously installed without");
-                            }
-                        }
-                    } break;
-                }
-            } else {
-                if (permissionsState.revokeInstallPermission(bp) !=
-                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                    // Also drop the permission flags.
-                    permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
-                            PackageManager.MASK_PERMISSION_FLAGS, 0);
-                    changedInstallPermission = true;
-                    Slog.i(TAG, "Un-granting permission " + perm
-                            + " from package " + pkg.packageName
-                            + " (protectionLevel=" + bp.getProtectionLevel()
-                            + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
-                            + ")");
-                } else if (bp.isAppOp()) {
-                    // Don't print warning for app op permissions, since it is fine for them
-                    // not to be granted, there is a UI for the user to decide.
-                    if (DEBUG_PERMISSIONS
-                            && (packageOfInterest == null
-                                    || packageOfInterest.equals(pkg.packageName))) {
-                        Slog.i(TAG, "Not granting permission " + perm
-                                + " to package " + pkg.packageName
-                                + " (protectionLevel=" + bp.getProtectionLevel()
-                                + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
-                                + ")");
-                    }
-                }
-            }
-        }
-
-        if ((changedInstallPermission || replace) && !ps.installPermissionsFixed &&
-                !isSystemApp(ps) || isUpdatedSystemApp(ps)){
-            // This is the first that we have heard about this package, so the
-            // permissions we have now selected are fixed until explicitly
-            // changed.
-            ps.installPermissionsFixed = true;
-        }
-
-        // Persist the runtime permissions state for users with changes. If permissions
-        // were revoked because no app in the shared user declares them we have to
-        // write synchronously to avoid losing runtime permissions state.
-        for (int userId : changedRuntimePermissionUserIds) {
-            mSettings.writeRuntimePermissionsForUserLPr(userId, runtimePermissionsRevoked);
-        }
-    }
-
-    private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
-        boolean allowed = false;
-        final int NP = PackageParser.NEW_PERMISSIONS.length;
-        for (int ip=0; ip<NP; ip++) {
-            final PackageParser.NewPermissionInfo npi
-                    = PackageParser.NEW_PERMISSIONS[ip];
-            if (npi.name.equals(perm)
-                    && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) {
-                allowed = true;
-                Log.i(TAG, "Auto-granting " + perm + " to old pkg "
-                        + pkg.packageName);
-                break;
-            }
-        }
-        return allowed;
-    }
-
-    /**
-     * Determines whether a package is whitelisted for a particular privapp permission.
-     *
-     * <p>Does NOT check whether the package is a privapp, just whether it's whitelisted.
-     *
-     * <p>This handles parent/child apps.
-     */
-    private boolean hasPrivappWhitelistEntry(String perm, PackageParser.Package pkg) {
-        ArraySet<String> wlPermissions = SystemConfig.getInstance()
-                .getPrivAppPermissions(pkg.packageName);
-        // Let's check if this package is whitelisted...
-        boolean whitelisted = wlPermissions != null && wlPermissions.contains(perm);
-        // If it's not, we'll also tail-recurse to the parent.
-        return whitelisted ||
-                pkg.parentPackage != null && hasPrivappWhitelistEntry(perm, pkg.parentPackage);
-    }
-
-    private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
-            BasePermission bp, PermissionsState origPermissions) {
-        boolean oemPermission = bp.isOEM();
-        boolean privilegedPermission = bp.isPrivileged();
-        boolean privappPermissionsDisable =
-                RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE;
-        boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName());
-        boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName);
-        if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivilegedApp()
-                && !platformPackage && platformPermission) {
-            if (!hasPrivappWhitelistEntry(perm, pkg)) {
-                Slog.w(TAG, "Privileged permission " + perm + " for package "
-                        + pkg.packageName + " - not in privapp-permissions whitelist");
-                // Only report violations for apps on system image
-                if (!mSystemReady && !pkg.isUpdatedSystemApp()) {
-                    // it's only a reportable violation if the permission isn't explicitly denied
-                    final ArraySet<String> deniedPermissions = SystemConfig.getInstance()
-                            .getPrivAppDenyPermissions(pkg.packageName);
-                    final boolean permissionViolation =
-                            deniedPermissions == null || !deniedPermissions.contains(perm);
-                    if (permissionViolation
-                            && RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
-                        if (mPrivappPermissionsViolations == null) {
-                            mPrivappPermissionsViolations = new ArraySet<>();
-                        }
-                        mPrivappPermissionsViolations.add(pkg.packageName + ": " + perm);
-                    } else {
-                        return false;
-                    }
-                }
-                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
-                    return false;
-                }
-            }
-        }
-        boolean allowed = (compareSignatures(
-                bp.getSourcePackageSetting().signatures.mSignatures, pkg.mSignatures)
-                        == PackageManager.SIGNATURE_MATCH)
-                || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures)
-                        == PackageManager.SIGNATURE_MATCH);
-        if (!allowed && (privilegedPermission || oemPermission)) {
-            if (isSystemApp(pkg)) {
-                // For updated system applications, a privileged/oem permission
-                // is granted only if it had been defined by the original application.
-                if (pkg.isUpdatedSystemApp()) {
-                    final PackageSetting sysPs = mSettings
-                            .getDisabledSystemPkgLPr(pkg.packageName);
-                    if (sysPs != null && sysPs.getPermissionsState().hasInstallPermission(perm)) {
-                        // If the original was granted this permission, we take
-                        // that grant decision as read and propagate it to the
-                        // update.
-                        if ((privilegedPermission && sysPs.isPrivileged())
-                                || (oemPermission && sysPs.isOem()
-                                        && canGrantOemPermission(sysPs, perm))) {
-                            allowed = true;
-                        }
-                    } else {
-                        // The system apk may have been updated with an older
-                        // version of the one on the data partition, but which
-                        // granted a new system permission that it didn't have
-                        // before.  In this case we do want to allow the app to
-                        // now get the new permission if the ancestral apk is
-                        // privileged to get it.
-                        if (sysPs != null && sysPs.pkg != null
-                                && isPackageRequestingPermission(sysPs.pkg, perm)
-                                && ((privilegedPermission && sysPs.isPrivileged())
-                                        || (oemPermission && sysPs.isOem()
-                                                && canGrantOemPermission(sysPs, perm)))) {
-                            allowed = true;
-                        }
-                        // Also if a privileged parent package on the system image or any of
-                        // its children requested a privileged/oem permission, the updated child
-                        // packages can also get the permission.
-                        if (pkg.parentPackage != null) {
-                            final PackageSetting disabledSysParentPs = mSettings
-                                    .getDisabledSystemPkgLPr(pkg.parentPackage.packageName);
-                            final PackageParser.Package disabledSysParentPkg =
-                                    (disabledSysParentPs == null || disabledSysParentPs.pkg == null)
-                                    ? null : disabledSysParentPs.pkg;
-                            if (disabledSysParentPkg != null
-                                    && ((privilegedPermission && disabledSysParentPs.isPrivileged())
-                                            || (oemPermission && disabledSysParentPs.isOem()))) {
-                                if (isPackageRequestingPermission(disabledSysParentPkg, perm)
-                                        && canGrantOemPermission(disabledSysParentPs, perm)) {
-                                    allowed = true;
-                                } else if (disabledSysParentPkg.childPackages != null) {
-                                    final int count = disabledSysParentPkg.childPackages.size();
-                                    for (int i = 0; i < count; i++) {
-                                        final PackageParser.Package disabledSysChildPkg =
-                                                disabledSysParentPkg.childPackages.get(i);
-                                        final PackageSetting disabledSysChildPs =
-                                                mSettings.getDisabledSystemPkgLPr(
-                                                        disabledSysChildPkg.packageName);
-                                        if (isPackageRequestingPermission(disabledSysChildPkg, perm)
-                                                && canGrantOemPermission(
-                                                        disabledSysChildPs, perm)) {
-                                            allowed = true;
-                                            break;
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                } else {
-                    allowed = (privilegedPermission && isPrivilegedApp(pkg))
-                            || (oemPermission && isOemApp(pkg)
-                                    && canGrantOemPermission(
-                                            mSettings.getPackageLPr(pkg.packageName), perm));
-                }
-            }
-        }
-        if (!allowed) {
-            if (!allowed
-                    && bp.isPre23()
-                    && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
-                // If this was a previously normal/dangerous permission that got moved
-                // to a system permission as part of the runtime permission redesign, then
-                // we still want to blindly grant it to old apps.
-                allowed = true;
-            }
-            if (!allowed && bp.isInstaller()
-                    && pkg.packageName.equals(mRequiredInstallerPackage)) {
-                // If this permission is to be granted to the system installer and
-                // this app is an installer, then it gets the permission.
-                allowed = true;
-            }
-            if (!allowed && bp.isVerifier()
-                    && pkg.packageName.equals(mRequiredVerifierPackage)) {
-                // If this permission is to be granted to the system verifier and
-                // this app is a verifier, then it gets the permission.
-                allowed = true;
-            }
-            if (!allowed && bp.isPreInstalled()
-                    && isSystemApp(pkg)) {
-                // Any pre-installed system app is allowed to get this permission.
-                allowed = true;
-            }
-            if (!allowed && bp.isDevelopment()) {
-                // For development permissions, a development permission
-                // is granted only if it was already granted.
-                allowed = origPermissions.hasInstallPermission(perm);
-            }
-            if (!allowed && bp.isSetup()
-                    && pkg.packageName.equals(mSetupWizardPackage)) {
-                // If this permission is to be granted to the system setup wizard and
-                // this app is a setup wizard, then it gets the permission.
-                allowed = true;
-            }
-        }
-        return allowed;
-    }
-
-    private static boolean canGrantOemPermission(PackageSetting ps, String permission) {
-        if (!ps.isOem()) {
-            return false;
-        }
-        // all oem permissions must explicitly be granted or denied
-        final Boolean granted =
-                SystemConfig.getInstance().getOemPermissions(ps.name).get(permission);
-        if (granted == null) {
-            throw new IllegalStateException("OEM permission" + permission + " requested by package "
-                    + ps.name + " must be explicitly declared granted or not");
-        }
-        return Boolean.TRUE == granted;
-    }
-
-    private boolean isPackageRequestingPermission(PackageParser.Package pkg, String permission) {
-        final int permCount = pkg.requestedPermissions.size();
-        for (int j = 0; j < permCount; j++) {
-            String requestedPermission = pkg.requestedPermissions.get(j);
-            if (permission.equals(requestedPermission)) {
-                return true;
-            }
-        }
-        return false;
-    }
 
     final class ActivityIntentResolver
             extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> {
@@ -16369,7 +15751,7 @@
             }
 
             // don't allow a system upgrade unless the upgrade hash matches
-            if (oldPackage.restrictUpdateHash != null && oldPackage.isSystemApp()) {
+            if (oldPackage.restrictUpdateHash != null && oldPackage.isSystem()) {
                 byte[] digestBytes = null;
                 try {
                     final MessageDigest digest = MessageDigest.getInstance("SHA-512");
@@ -16636,7 +16018,9 @@
                     setInstallerPackageNameLPw(deletedPackage, installerPackageName);
 
                     // Update permissions for restored package
-                    updatePermissionsLPw(deletedPackage, UPDATE_PERMISSIONS_ALL);
+                    mPermissionManager.updatePermissions(
+                            deletedPackage.packageName, deletedPackage, false, mPackages.values(),
+                            mPermissionCallback);
 
                     mSettings.writeLPr();
                 }
@@ -16778,7 +16162,9 @@
                 setInstallerPackageNameLPw(deletedPackage, installerPackageName);
 
                 // Update permissions for restored package
-                updatePermissionsLPw(deletedPackage, UPDATE_PERMISSIONS_ALL);
+                mPermissionManager.updatePermissions(
+                        deletedPackage.packageName, deletedPackage, false, mPackages.values(),
+                        mPermissionCallback);
 
                 mSettings.writeLPr();
             }
@@ -16891,12 +16277,12 @@
         }
     }
 
-    private void updateSettingsInternalLI(PackageParser.Package newPackage,
+    private void updateSettingsInternalLI(PackageParser.Package pkg,
             String installerPackageName, int[] allUsers, int[] installedForUsers,
             PackageInstalledInfo res, UserHandle user, int installReason) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
 
-        String pkgName = newPackage.packageName;
+        String pkgName = pkg.packageName;
         synchronized (mPackages) {
             //write settings. the installStatus will be incomplete at this stage.
             //note that the new package setting would have already been
@@ -16908,18 +16294,18 @@
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
-        if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.codePath);
+        if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.codePath);
         synchronized (mPackages) {
-            updatePermissionsLPw(newPackage.packageName, newPackage,
-                    UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0
-                            ? UPDATE_PERMISSIONS_ALL : 0));
+// NOTE: This changes slightly to include UPDATE_PERMISSIONS_ALL regardless of the size of pkg.permissions
+            mPermissionManager.updatePermissions(pkg.packageName, pkg, true, mPackages.values(),
+                    mPermissionCallback);
             // For system-bundled packages, we assume that installing an upgraded version
             // of the package implies that the user actually wants to run that new code,
             // so we enable the package.
             PackageSetting ps = mSettings.mPackages.get(pkgName);
             final int userId = user.getIdentifier();
             if (ps != null) {
-                if (isSystemApp(newPackage)) {
+                if (isSystemApp(pkg)) {
                     if (DEBUG_INSTALL) {
                         Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName);
                     }
@@ -16979,8 +16365,8 @@
                 mSettings.writeKernelMappingLPr(ps);
             }
             res.name = pkgName;
-            res.uid = newPackage.applicationInfo.uid;
-            res.pkg = newPackage;
+            res.uid = pkg.applicationInfo.uid;
+            res.pkg = pkg;
             mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE);
             mSettings.setInstallerPackageName(pkgName, installerPackageName);
             res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
@@ -17679,18 +17065,6 @@
         return installFlags;
     }
 
-    private String getVolumeUuidForPackage(PackageParser.Package pkg) {
-        if (isExternal(pkg)) {
-            if (TextUtils.isEmpty(pkg.volumeUuid)) {
-                return StorageManager.UUID_PRIMARY_PHYSICAL;
-            } else {
-                return pkg.volumeUuid;
-            }
-        } else {
-            return StorageManager.UUID_PRIVATE_INTERNAL;
-        }
-    }
-
     private VersionInfo getSettingsVersionForPackage(PackageParser.Package pkg) {
         if (isExternal(pkg)) {
             if (TextUtils.isEmpty(pkg.volumeUuid)) {
@@ -18335,7 +17709,8 @@
                     if (outInfo != null) {
                         outInfo.removedAppId = removedAppId;
                     }
-                    updatePermissionsLPw(deletedPs.name, null, 0);
+                    mPermissionManager.updatePermissions(
+                            deletedPs.name, null, false, mPackages.values(), mPermissionCallback);
                     if (deletedPs.sharedUser != null) {
                         // Remove permissions associated with package. Since runtime
                         // permissions are per user we have to kill the removed package
@@ -18538,21 +17913,21 @@
             parseFlags |= PackageParser.PARSE_IS_OEM;
         }
 
-        final PackageParser.Package newPkg =
+        final PackageParser.Package pkg =
                 scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, 0 /*currentTime*/, null);
 
         try {
             // update shared libraries for the newly re-installed system package
-            updateSharedLibrariesLPr(newPkg, null);
+            updateSharedLibrariesLPr(pkg, null);
         } catch (PackageManagerException e) {
             Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
         }
 
-        prepareAppDataAfterInstallLIF(newPkg);
+        prepareAppDataAfterInstallLIF(pkg);
 
         // writer
         synchronized (mPackages) {
-            PackageSetting ps = mSettings.mPackages.get(newPkg.packageName);
+            PackageSetting ps = mSettings.mPackages.get(pkg.packageName);
 
             // Propagate the permissions state as we do not want to drop on the floor
             // runtime permissions. The update permissions method below will take
@@ -18560,8 +17935,8 @@
             if (origPermissionState != null) {
                 ps.getPermissionsState().copyFrom(origPermissionState);
             }
-            updatePermissionsLPw(newPkg.packageName, newPkg,
-                    UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
+            mPermissionManager.updatePermissions(pkg.packageName, pkg, true, mPackages.values(),
+                    mPermissionCallback);
 
             final boolean applyUserRestrictions
                     = (allUserHandles != null) && (origUserHandles != null);
@@ -18594,7 +17969,7 @@
                 mSettings.writeLPr();
             }
         }
-        return newPkg;
+        return pkg;
     }
 
     private boolean deleteInstalledPackageLIF(PackageSetting ps,
@@ -19211,7 +18586,7 @@
             // If permission review is enabled and this is a legacy app, mark the
             // permission as requiring a review as this is the initial state.
             int flags = 0;
-            if (mPermissionReviewRequired
+            if (mSettings.mPermissions.mPermissionReviewRequired
                     && ps.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
                 flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
             }
@@ -20570,7 +19945,7 @@
             // data partition and then replace the version on the system partition.
             final PackageParser.Package deletedPkg = pkgSetting.pkg;
             final boolean isSystemStub = deletedPkg.isStub
-                    && deletedPkg.isSystemApp();
+                    && deletedPkg.isSystem();
             if (isSystemStub
                     && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                             || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
@@ -20610,22 +19985,23 @@
                     synchronized (mPackages) {
                         disableSystemPackageLPw(deletedPkg, tmpPkg);
                     }
-                    final PackageParser.Package newPkg;
+                    final PackageParser.Package pkg;
                     try (PackageFreezer freezer =
                             freezePackage(deletedPkg.packageName, "setEnabledSetting")) {
                         final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                                 | PackageParser.PARSE_ENFORCE_CODE;
-                        newPkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/,
+                        pkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/,
                                 0 /*currentTime*/, null /*user*/);
-                        prepareAppDataAfterInstallLIF(newPkg);
+                        prepareAppDataAfterInstallLIF(pkg);
                         synchronized (mPackages) {
                             try {
-                                updateSharedLibrariesLPr(newPkg, null);
+                                updateSharedLibrariesLPr(pkg, null);
                             } catch (PackageManagerException e) {
                                 Slog.e(TAG, "updateAllSharedLibrariesLPw failed: ", e);
                             }
-                            updatePermissionsLPw(newPkg.packageName, newPkg,
-                                    UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
+                            mPermissionManager.updatePermissions(
+                                    pkg.packageName, pkg, true, mPackages.values(),
+                                    mPermissionCallback);
                             mSettings.writeLPr();
                         }
                     } catch (PackageManagerException e) {
@@ -20665,11 +20041,11 @@
                         }
                         return;
                     }
-                    clearAppDataLIF(newPkg, UserHandle.USER_ALL, FLAG_STORAGE_DE
+                    clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE
                             | FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
-                    clearAppProfilesLIF(newPkg, UserHandle.USER_ALL);
-                    mDexManager.notifyPackageUpdated(newPkg.packageName,
-                            newPkg.baseCodePath, newPkg.splitCodePaths);
+                    clearAppProfilesLIF(pkg, UserHandle.USER_ALL);
+                    mDexManager.notifyPackageUpdated(pkg.packageName,
+                            pkg.baseCodePath, pkg.splitCodePaths);
                 }
             }
             if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
@@ -20980,8 +20356,9 @@
         // permissions, ensure permissions are updated. Beware of dragons if you
         // try optimizing this.
         synchronized (mPackages) {
-            updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL,
-                    UPDATE_PERMISSIONS_ALL);
+            mPermissionManager.updateAllPermissions(
+                    StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(),
+                    mPermissionCallback);
         }
 
         // Kick off any messages waiting for system ready
@@ -21030,10 +20407,7 @@
         sUserManager.reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL);
         reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL);
 
-        if (mPrivappPermissionsViolations != null) {
-            throw new IllegalStateException("Signature|privileged permissions not in "
-                    + "privapp-permissions whitelist: " + mPrivappPermissionsViolations);
-        }
+        mPermissionManager.systemReady();
     }
 
     public void waitForAppDataPrepared() {
@@ -22018,13 +21392,13 @@
         }
 
         synchronized (mPackages) {
-            int updateFlags = UPDATE_PERMISSIONS_ALL;
-            if (ver.sdkVersion != mSdkVersion) {
+            final boolean sdkUpdated = (ver.sdkVersion != mSdkVersion);
+            if (sdkUpdated) {
                 logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to "
                         + mSdkVersion + "; regranting permissions for " + volumeUuid);
-                updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
             }
-            updatePermissionsLocked(null, null, volumeUuid, updateFlags);
+            mPermissionManager.updateAllPermissions(volumeUuid, sdkUpdated, mPackages.values(),
+                    mPermissionCallback);
 
             // Yay, everything is now upgraded
             ver.forceCurrent();
@@ -22472,7 +21846,7 @@
      * requested by the app.
      */
     private boolean maybeMigrateAppDataLIF(PackageParser.Package pkg, int userId) {
-        if (pkg.isSystemApp() && !StorageManager.isFileEncryptedNativeOrEmulated()
+        if (pkg.isSystem() && !StorageManager.isFileEncryptedNativeOrEmulated()
                 && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
             final int storageTarget = pkg.applicationInfo.isDefaultToDeviceProtectedStorage()
                     ? StorageManager.FLAG_STORAGE_DE : StorageManager.FLAG_STORAGE_CE;
@@ -23037,9 +22411,11 @@
             // permissions to keep per user flag state whether review is needed.
             // Hence, if a new user is added we have to propagate dangerous
             // permission grants for these legacy apps.
-            if (mPermissionReviewRequired) {
-                updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
-                        | UPDATE_PERMISSIONS_REPLACE_ALL);
+            if (mSettings.mPermissions.mPermissionReviewRequired) {
+// NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG
+                mPermissionManager.updateAllPermissions(
+                        StorageManager.UUID_PRIVATE_INTERNAL, true, mPackages.values(),
+                        mPermissionCallback);
             }
         }
     }
@@ -23621,24 +22997,8 @@
         @Override
         public boolean isPermissionsReviewRequired(String packageName, int userId) {
             synchronized (mPackages) {
-                // If we do not support permission review, done.
-                if (!mPermissionReviewRequired) {
-                    return false;
-                }
-
-                PackageSetting packageSetting = mSettings.mPackages.get(packageName);
-                if (packageSetting == null) {
-                    return false;
-                }
-
-                // Permission review applies only to apps not supporting the new permission model.
-                if (packageSetting.pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
-                    return false;
-                }
-
-                // Legacy apps have the permission and get user consent on launch.
-                PermissionsState permissionsState = packageSetting.getPermissionsState();
-                return permissionsState.isPermissionReviewRequired(userId);
+                return mPermissionManager.isPermissionsReviewRequired(
+                        mPackages.get(packageName), userId);
             }
         }
 
@@ -23792,6 +23152,16 @@
         }
 
         @Override
+        public boolean isLegacySystemApp(Package pkg) {
+            synchronized (mPackages) {
+                final PackageSetting ps = (PackageSetting) pkg.mExtras;
+                return mPromoteSystemApps
+                        && ps.isSystem()
+                        && mExistingSystemPackages.contains(ps.name);
+            }
+        }
+
+        @Override
         public List<PackageInfo> getOverlayPackages(int userId) {
             final ArrayList<PackageInfo> overlayPackages = new ArrayList<PackageInfo>();
             synchronized (mPackages) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 1fea003..a2099e6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -16,14 +16,17 @@
 
 package com.android.server.pm;
 
+import android.accounts.IAccountManager;
 import android.app.ActivityManager;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
 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.IPackageManager;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
@@ -41,6 +44,7 @@
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
 import android.content.pm.VersionedPackage;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
@@ -49,15 +53,23 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IUserManager;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.ShellCommand;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
 import android.text.TextUtils;
-import android.util.ArrayMap;
+import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.PrintWriterPrinter;
+
 import com.android.internal.content.PackageHelper;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.SizedInputStream;
 import com.android.server.SystemConfig;
 
@@ -81,6 +93,12 @@
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.TimeUnit;
 
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+
 class PackageManagerShellCommand extends ShellCommand {
     /** Path for streaming APK content */
     private static final String STDIN_PATH = "-";
@@ -107,6 +125,20 @@
         final PrintWriter pw = getOutPrintWriter();
         try {
             switch(cmd) {
+                case "path":
+                    return runPath();
+                case "dump":
+                    return runDump();
+                case "list":
+                    return runList();
+                case "resolve-activity":
+                    return runResolveActivity();
+                case "query-activities":
+                    return runQueryIntentActivities();
+                case "query-services":
+                    return runQueryIntentServices();
+                case "query-receivers":
+                    return runQueryIntentReceivers();
                 case "install":
                     return runInstall();
                 case "install-abandon":
@@ -122,44 +154,99 @@
                     return runInstallWrite();
                 case "install-existing":
                     return runInstallExisting();
+                case "set-install-location":
+                    return runSetInstallLocation();
+                case "get-install-location":
+                    return runGetInstallLocation();
+                case "move-package":
+                    return runMovePackage();
+                case "move-primary-storage":
+                    return runMovePrimaryStorage();
                 case "compile":
                     return runCompile();
                 case "reconcile-secondary-dex-files":
                     return runreconcileSecondaryDexFiles();
+                case "force-dex-opt":
+                    return runForceDexOpt();
                 case "bg-dexopt-job":
                     return runDexoptJob();
                 case "dump-profiles":
                     return runDumpProfiles();
-                case "list":
-                    return runList();
                 case "uninstall":
                     return runUninstall();
-                case "resolve-activity":
-                    return runResolveActivity();
-                case "query-activities":
-                    return runQueryIntentActivities();
-                case "query-services":
-                    return runQueryIntentServices();
-                case "query-receivers":
-                    return runQueryIntentReceivers();
+                case "clear":
+                    return runClear();
+                case "enable":
+                    return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+                case "disable":
+                    return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+                case "disable-user":
+                    return runSetEnabledSetting(
+                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
+                case "disable-until-used":
+                    return runSetEnabledSetting(
+                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
+                case "default-state":
+                    return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+                case "hide":
+                    return runSetHiddenSetting(true);
+                case "unhide":
+                    return runSetHiddenSetting(false);
                 case "suspend":
                     return runSuspend(true);
                 case "unsuspend":
                     return runSuspend(false);
-                case "set-home-activity":
-                    return runSetHomeActivity();
+                case "grant":
+                    return runGrantRevokePermission(true);
+                case "revoke":
+                    return runGrantRevokePermission(false);
+                case "reset-permissions":
+                    return runResetPermissions();
+                case "set-permission-enforced":
+                    return runSetPermissionEnforced();
                 case "get-privapp-permissions":
                     return runGetPrivappPermissions();
                 case "get-privapp-deny-permissions":
                     return runGetPrivappDenyPermissions();
                 case "get-oem-permissions":
                     return runGetOemPermissions();
+                case "set-app-link":
+                    return runSetAppLink();
+                case "get-app-link":
+                    return runGetAppLink();
+                case "trim-caches":
+                    return runTrimCaches();
+                case "create-user":
+                    return runCreateUser();
+                case "remove-user":
+                    return runRemoveUser();
+                case "set-user-restriction":
+                    return runSetUserRestriction();
+                case "get-max-users":
+                    return runGetMaxUsers();
+                case "set-home-activity":
+                    return runSetHomeActivity();
+                case "set-installer":
+                    return runSetInstaller();
                 case "get-instantapp-resolver":
                     return runGetInstantAppResolver();
                 case "has-feature":
                     return runHasFeature();
-                default:
+                default: {
+                    String nextArg = getNextArg();
+                    if (nextArg == null) {
+                        if (cmd.equalsIgnoreCase("-l")) {
+                            return runListPackages(false);
+                        } else if (cmd.equalsIgnoreCase("-lf")) {
+                            return runListPackages(true);
+                        }
+                    } else if (getNextArg() == null) {
+                        if (cmd.equalsIgnoreCase("-p")) {
+                            return displayPackageFilePath(nextArg, UserHandle.USER_SYSTEM);
+                        }
+                    }
                     return handleDefaultCommands(cmd);
+                }
             }
         } catch (RemoteException e) {
             pw.println("Remote exception: " + e);
@@ -195,346 +282,40 @@
             }
         }
     }
-
-    private int runInstall() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        final InstallParams params = makeInstallParams();
-        final String inPath = getNextArg();
-
-        setParamsSize(params, inPath);
-        final int sessionId = doCreateSession(params.sessionParams,
-                params.installerPackageName, params.userId);
-        boolean abandonSession = true;
-        try {
-            if (inPath == null && params.sessionParams.sizeBytes == -1) {
-                pw.println("Error: must either specify a package size or an APK file");
-                return 1;
-            }
-            if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
-                    false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            if (doCommitSession(sessionId, false /*logSuccess*/)
-                    != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            abandonSession = false;
-            pw.println("Success");
-            return 0;
-        } finally {
-            if (abandonSession) {
-                try {
-                    doAbandonSession(sessionId, false /*logSuccess*/);
-                } catch (Exception ignore) {
+    /**
+     * Displays the package file for a package.
+     * @param pckg
+     */
+    private int displayPackageFilePath(String pckg, int userId) throws RemoteException {
+        PackageInfo info = mInterface.getPackageInfo(pckg, 0, userId);
+        if (info != null && info.applicationInfo != null) {
+            final PrintWriter pw = getOutPrintWriter();
+            pw.print("package:");
+            pw.println(info.applicationInfo.sourceDir);
+            if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
+                for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
+                    pw.print("package:");
+                    pw.println(splitSourceDir);
                 }
             }
+            return 0;
         }
+        return 1;
     }
 
-    private int runSuspend(boolean suspendedState) {
-        final PrintWriter pw = getOutPrintWriter();
+    private int runPath() throws RemoteException {
         int userId = UserHandle.USER_SYSTEM;
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "--user":
-                    userId = UserHandle.parseUserArg(getNextArgRequired());
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
+        String option = getNextOption();
+        if (option != null && option.equals("--user")) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
         }
 
-        String packageName = getNextArg();
-        if (packageName == null) {
-            pw.println("Error: package name not specified");
+        String pkg = getNextArgRequired();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified");
             return 1;
         }
-
-        try {
-            mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
-                    userId);
-            pw.println("Package " + packageName + " new suspended state: "
-                    + mInterface.isPackageSuspendedForUser(packageName, userId));
-            return 0;
-        } catch (RemoteException | IllegalArgumentException e) {
-            pw.println(e.toString());
-            return 1;
-        }
-    }
-
-    private int runInstallAbandon() throws RemoteException {
-        final int sessionId = Integer.parseInt(getNextArg());
-        return doAbandonSession(sessionId, true /*logSuccess*/);
-    }
-
-    private int runInstallCommit() throws RemoteException {
-        final int sessionId = Integer.parseInt(getNextArg());
-        return doCommitSession(sessionId, true /*logSuccess*/);
-    }
-
-    private int runInstallCreate() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        final InstallParams installParams = makeInstallParams();
-        final int sessionId = doCreateSession(installParams.sessionParams,
-                installParams.installerPackageName, installParams.userId);
-
-        // NOTE: adb depends on parsing this string
-        pw.println("Success: created install session [" + sessionId + "]");
-        return 0;
-    }
-
-    private int runInstallWrite() throws RemoteException {
-        long sizeBytes = -1;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            if (opt.equals("-S")) {
-                sizeBytes = Long.parseLong(getNextArg());
-            } else {
-                throw new IllegalArgumentException("Unknown option: " + opt);
-            }
-        }
-
-        final int sessionId = Integer.parseInt(getNextArg());
-        final String splitName = getNextArg();
-        final String path = getNextArg();
-        return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
-    }
-
-    private int runInstallRemove() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-
-        final int sessionId = Integer.parseInt(getNextArg());
-
-        final String splitName = getNextArg();
-        if (splitName == null) {
-            pw.println("Error: split name not specified");
-            return 1;
-        }
-        return doRemoveSplit(sessionId, splitName, true /*logSuccess*/);
-    }
-
-    private int runInstallExisting() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        int userId = UserHandle.USER_SYSTEM;
-        int installFlags = 0;
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "--user":
-                    userId = UserHandle.parseUserArg(getNextArgRequired());
-                    break;
-                case "--ephemeral":
-                case "--instant":
-                    installFlags |= PackageManager.INSTALL_INSTANT_APP;
-                    installFlags &= ~PackageManager.INSTALL_FULL_APP;
-                    break;
-                case "--full":
-                    installFlags &= ~PackageManager.INSTALL_INSTANT_APP;
-                    installFlags |= PackageManager.INSTALL_FULL_APP;
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
-        }
-
-        final String packageName = getNextArg();
-        if (packageName == null) {
-            pw.println("Error: package name not specified");
-            return 1;
-        }
-
-        try {
-            final int res = mInterface.installExistingPackageAsUser(packageName, userId,
-                    installFlags, PackageManager.INSTALL_REASON_UNKNOWN);
-            if (res == PackageManager.INSTALL_FAILED_INVALID_URI) {
-                throw new NameNotFoundException("Package " + packageName + " doesn't exist");
-            }
-            pw.println("Package " + packageName + " installed for user: " + userId);
-            return 0;
-        } catch (RemoteException | NameNotFoundException e) {
-            pw.println(e.toString());
-            return 1;
-        }
-    }
-
-    private int runCompile() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
-        boolean forceCompilation = false;
-        boolean allPackages = false;
-        boolean clearProfileData = false;
-        String compilerFilter = null;
-        String compilationReason = null;
-        String checkProfilesRaw = null;
-        boolean secondaryDex = false;
-        String split = null;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "-a":
-                    allPackages = true;
-                    break;
-                case "-c":
-                    clearProfileData = true;
-                    break;
-                case "-f":
-                    forceCompilation = true;
-                    break;
-                case "-m":
-                    compilerFilter = getNextArgRequired();
-                    break;
-                case "-r":
-                    compilationReason = getNextArgRequired();
-                    break;
-                case "--check-prof":
-                    checkProfilesRaw = getNextArgRequired();
-                    break;
-                case "--reset":
-                    forceCompilation = true;
-                    clearProfileData = true;
-                    compilationReason = "install";
-                    break;
-                case "--secondary-dex":
-                    secondaryDex = true;
-                    break;
-                case "--split":
-                    split = getNextArgRequired();
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
-        }
-
-        if (checkProfilesRaw != null) {
-            if ("true".equals(checkProfilesRaw)) {
-                checkProfiles = true;
-            } else if ("false".equals(checkProfilesRaw)) {
-                checkProfiles = false;
-            } else {
-                pw.println("Invalid value for \"--check-prof\". Expected \"true\" or \"false\".");
-                return 1;
-            }
-        }
-
-        if (compilerFilter != null && compilationReason != null) {
-            pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") " +
-                    "at the same time");
-            return 1;
-        }
-        if (compilerFilter == null && compilationReason == null) {
-            pw.println("Cannot run without any of compilation filter (\"-m\") and compilation " +
-                    "reason (\"-r\") at the same time");
-            return 1;
-        }
-
-        if (allPackages && split != null) {
-            pw.println("-a cannot be specified together with --split");
-            return 1;
-        }
-
-        if (secondaryDex && split != null) {
-            pw.println("--secondary-dex cannot be specified together with --split");
-            return 1;
-        }
-
-        String targetCompilerFilter;
-        if (compilerFilter != null) {
-            if (!DexFile.isValidCompilerFilter(compilerFilter)) {
-                pw.println("Error: \"" + compilerFilter +
-                        "\" is not a valid compilation filter.");
-                return 1;
-            }
-            targetCompilerFilter = compilerFilter;
-        } else {
-            int reason = -1;
-            for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
-                if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals(
-                        compilationReason)) {
-                    reason = i;
-                    break;
-                }
-            }
-            if (reason == -1) {
-                pw.println("Error: Unknown compilation reason: " + compilationReason);
-                return 1;
-            }
-            targetCompilerFilter =
-                    PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason);
-        }
-
-
-        List<String> packageNames = null;
-        if (allPackages) {
-            packageNames = mInterface.getAllPackages();
-        } else {
-            String packageName = getNextArg();
-            if (packageName == null) {
-                pw.println("Error: package name not specified");
-                return 1;
-            }
-            packageNames = Collections.singletonList(packageName);
-        }
-
-        List<String> failedPackages = new ArrayList<>();
-        for (String packageName : packageNames) {
-            if (clearProfileData) {
-                mInterface.clearApplicationProfileData(packageName);
-            }
-
-            boolean result = secondaryDex
-                    ? mInterface.performDexOptSecondary(packageName,
-                            targetCompilerFilter, forceCompilation)
-                    : mInterface.performDexOptMode(packageName,
-                            checkProfiles, targetCompilerFilter, forceCompilation,
-                            true /* bootComplete */, split);
-            if (!result) {
-                failedPackages.add(packageName);
-            }
-        }
-
-        if (failedPackages.isEmpty()) {
-            pw.println("Success");
-            return 0;
-        } else if (failedPackages.size() == 1) {
-            pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled");
-            return 1;
-        } else {
-            pw.print("Failure: the following packages could not be compiled: ");
-            boolean is_first = true;
-            for (String packageName : failedPackages) {
-                if (is_first) {
-                    is_first = false;
-                } else {
-                    pw.print(", ");
-                }
-                pw.print(packageName);
-            }
-            pw.println();
-            return 1;
-        }
-    }
-
-    private int runreconcileSecondaryDexFiles() throws RemoteException {
-        String packageName = getNextArg();
-        mInterface.reconcileSecondaryDexFiles(packageName);
-        return 0;
-    }
-
-    private int runDexoptJob() throws RemoteException {
-        boolean result = mInterface.runBackgroundDexoptJob();
-        return result ? 0 : -1;
-    }
-
-    private int runDumpProfiles() throws RemoteException {
-        String packageName = getNextArg();
-        mInterface.dumpProfiles(packageName);
-        return 0;
+        return displayPackageFilePath(pkg, userId);
     }
 
     private int runList() throws RemoteException {
@@ -558,6 +339,11 @@
                 return runListPermissionGroups();
             case "permissions":
                 return runListPermissions();
+            case "users":
+                ServiceManager.getService("user").shellCommand(
+                        getInFileDescriptor(), getOutFileDescriptor(), getErrFileDescriptor(),
+                        new String[] { "list" }, getShellCallback(), adoptResultReceiver());
+                return 0;
         }
         pw.println("Error: unknown list type '" + type + "'");
         return -1;
@@ -590,7 +376,7 @@
                 pw.println();
             } else {
                 pw.println("reqGlEsVersion=0x"
-                    + Integer.toHexString(fi.reqGlEsVersion));
+                        + Integer.toHexString(fi.reqGlEsVersion));
             }
         }
         return 0;
@@ -872,111 +658,6 @@
         return 0;
     }
 
-    private int runUninstall() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        int flags = 0;
-        int userId = UserHandle.USER_ALL;
-        int versionCode = PackageManager.VERSION_CODE_HIGHEST;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "-k":
-                    flags |= PackageManager.DELETE_KEEP_DATA;
-                    break;
-                case "--user":
-                    userId = UserHandle.parseUserArg(getNextArgRequired());
-                    break;
-                case "--versionCode":
-                    versionCode = Integer.parseInt(getNextArgRequired());
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
-        }
-
-        final String packageName = getNextArg();
-        if (packageName == null) {
-            pw.println("Error: package name not specified");
-            return 1;
-        }
-
-        // if a split is specified, just remove it and not the whole package
-        final String splitName = getNextArg();
-        if (splitName != null) {
-            return runRemoveSplit(packageName, splitName);
-        }
-
-        userId = translateUserId(userId, "runUninstall");
-        if (userId == UserHandle.USER_ALL) {
-            userId = UserHandle.USER_SYSTEM;
-            flags |= PackageManager.DELETE_ALL_USERS;
-        } else {
-            final PackageInfo info = mInterface.getPackageInfo(packageName,
-                    PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId);
-            if (info == null) {
-                pw.println("Failure [not installed for " + userId + "]");
-                return 1;
-            }
-            final boolean isSystem =
-                    (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-            // If we are being asked to delete a system app for just one
-            // user set flag so it disables rather than reverting to system
-            // version of the app.
-            if (isSystem) {
-                flags |= PackageManager.DELETE_SYSTEM_APP;
-            }
-        }
-
-        final LocalIntentReceiver receiver = new LocalIntentReceiver();
-        mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName,
-                versionCode), null /*callerPackageName*/, flags,
-                receiver.getIntentSender(), userId);
-
-        final Intent result = receiver.getResult();
-        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        if (status == PackageInstaller.STATUS_SUCCESS) {
-            pw.println("Success");
-            return 0;
-        } else {
-            pw.println("Failure ["
-                    + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
-            return 1;
-        }
-    }
-
-    private int runRemoveSplit(String packageName, String splitName) throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        final SessionParams sessionParams = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
-        sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
-        sessionParams.appPackageName = packageName;
-        final int sessionId =
-                doCreateSession(sessionParams, null /*installerPackageName*/, UserHandle.USER_ALL);
-        boolean abandonSession = true;
-        try {
-            if (doRemoveSplit(sessionId, splitName, false /*logSuccess*/)
-                    != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            if (doCommitSession(sessionId, false /*logSuccess*/)
-                    != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            abandonSession = false;
-            pw.println("Success");
-            return 0;
-        } finally {
-            if (abandonSession) {
-                try {
-                    doAbandonSession(sessionId, false /*logSuccess*/);
-                } catch (Exception ignore) {
-                }
-            }
-        }
-    }
-
     private Intent parseIntentAndUser() throws URISyntaxException {
         mTargetUser = UserHandle.USER_CURRENT;
         mBrief = false;
@@ -1154,6 +835,1029 @@
         return 0;
     }
 
+    private int runInstall() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final InstallParams params = makeInstallParams();
+        final String inPath = getNextArg();
+
+        setParamsSize(params, inPath);
+        final int sessionId = doCreateSession(params.sessionParams,
+                params.installerPackageName, params.userId);
+        boolean abandonSession = true;
+        try {
+            if (inPath == null && params.sessionParams.sizeBytes == -1) {
+                pw.println("Error: must either specify a package size or an APK file");
+                return 1;
+            }
+            if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
+                    false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+                return 1;
+            }
+            if (doCommitSession(sessionId, false /*logSuccess*/)
+                    != PackageInstaller.STATUS_SUCCESS) {
+                return 1;
+            }
+            abandonSession = false;
+            pw.println("Success");
+            return 0;
+        } finally {
+            if (abandonSession) {
+                try {
+                    doAbandonSession(sessionId, false /*logSuccess*/);
+                } catch (Exception ignore) {
+                }
+            }
+        }
+    }
+
+    private int runInstallAbandon() throws RemoteException {
+        final int sessionId = Integer.parseInt(getNextArg());
+        return doAbandonSession(sessionId, true /*logSuccess*/);
+    }
+
+    private int runInstallCommit() throws RemoteException {
+        final int sessionId = Integer.parseInt(getNextArg());
+        return doCommitSession(sessionId, true /*logSuccess*/);
+    }
+
+    private int runInstallCreate() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final InstallParams installParams = makeInstallParams();
+        final int sessionId = doCreateSession(installParams.sessionParams,
+                installParams.installerPackageName, installParams.userId);
+
+        // NOTE: adb depends on parsing this string
+        pw.println("Success: created install session [" + sessionId + "]");
+        return 0;
+    }
+
+    private int runInstallWrite() throws RemoteException {
+        long sizeBytes = -1;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("-S")) {
+                sizeBytes = Long.parseLong(getNextArg());
+            } else {
+                throw new IllegalArgumentException("Unknown option: " + opt);
+            }
+        }
+
+        final int sessionId = Integer.parseInt(getNextArg());
+        final String splitName = getNextArg();
+        final String path = getNextArg();
+        return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
+    }
+
+    private int runInstallRemove() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+
+        final int sessionId = Integer.parseInt(getNextArg());
+
+        final String splitName = getNextArg();
+        if (splitName == null) {
+            pw.println("Error: split name not specified");
+            return 1;
+        }
+        return doRemoveSplit(sessionId, splitName, true /*logSuccess*/);
+    }
+
+    private int runInstallExisting() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        int userId = UserHandle.USER_SYSTEM;
+        int installFlags = 0;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                case "--ephemeral":
+                case "--instant":
+                    installFlags |= PackageManager.INSTALL_INSTANT_APP;
+                    installFlags &= ~PackageManager.INSTALL_FULL_APP;
+                    break;
+                case "--full":
+                    installFlags &= ~PackageManager.INSTALL_INSTANT_APP;
+                    installFlags |= PackageManager.INSTALL_FULL_APP;
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        final String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+
+        try {
+            final int res = mInterface.installExistingPackageAsUser(packageName, userId,
+                    installFlags, PackageManager.INSTALL_REASON_UNKNOWN);
+            if (res == PackageManager.INSTALL_FAILED_INVALID_URI) {
+                throw new NameNotFoundException("Package " + packageName + " doesn't exist");
+            }
+            pw.println("Package " + packageName + " installed for user: " + userId);
+            return 0;
+        } catch (RemoteException | NameNotFoundException e) {
+            pw.println(e.toString());
+            return 1;
+        }
+    }
+
+    private int runSetInstallLocation() throws RemoteException {
+        int loc;
+
+        String arg = getNextArg();
+        if (arg == null) {
+            getErrPrintWriter().println("Error: no install location specified.");
+            return 1;
+        }
+        try {
+            loc = Integer.parseInt(arg);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: install location has to be a number.");
+            return 1;
+        }
+        if (!mInterface.setInstallLocation(loc)) {
+            getErrPrintWriter().println("Error: install location has to be a number.");
+            return 1;
+        }
+        return 0;
+    }
+
+    private int runGetInstallLocation() throws RemoteException {
+        int loc = mInterface.getInstallLocation();
+        String locStr = "invalid";
+        if (loc == PackageHelper.APP_INSTALL_AUTO) {
+            locStr = "auto";
+        } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) {
+            locStr = "internal";
+        } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) {
+            locStr = "external";
+        }
+        getOutPrintWriter().println(loc + "[" + locStr + "]");
+        return 0;
+    }
+
+    public int runMovePackage() throws RemoteException {
+        final String packageName = getNextArg();
+        if (packageName == null) {
+            getErrPrintWriter().println("Error: package name not specified");
+            return 1;
+        }
+        String volumeUuid = getNextArg();
+        if ("internal".equals(volumeUuid)) {
+            volumeUuid = null;
+        }
+
+        final int moveId = mInterface.movePackage(packageName, volumeUuid);
+
+        int status = mInterface.getMoveStatus(moveId);
+        while (!PackageManager.isMoveStatusFinished(status)) {
+            SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
+            status = mInterface.getMoveStatus(moveId);
+        }
+
+        if (status == PackageManager.MOVE_SUCCEEDED) {
+            getOutPrintWriter().println("Success");
+            return 0;
+        } else {
+            getErrPrintWriter().println("Failure [" + status + "]");
+            return 1;
+        }
+    }
+
+    public int runMovePrimaryStorage() throws RemoteException {
+        String volumeUuid = getNextArg();
+        if ("internal".equals(volumeUuid)) {
+            volumeUuid = null;
+        }
+
+        final int moveId = mInterface.movePrimaryStorage(volumeUuid);
+
+        int status = mInterface.getMoveStatus(moveId);
+        while (!PackageManager.isMoveStatusFinished(status)) {
+            SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
+            status = mInterface.getMoveStatus(moveId);
+        }
+
+        if (status == PackageManager.MOVE_SUCCEEDED) {
+            getOutPrintWriter().println("Success");
+            return 0;
+        } else {
+            getErrPrintWriter().println("Failure [" + status + "]");
+            return 1;
+        }
+    }
+
+    private int runCompile() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
+        boolean forceCompilation = false;
+        boolean allPackages = false;
+        boolean clearProfileData = false;
+        String compilerFilter = null;
+        String compilationReason = null;
+        String checkProfilesRaw = null;
+        boolean secondaryDex = false;
+        String split = null;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-a":
+                    allPackages = true;
+                    break;
+                case "-c":
+                    clearProfileData = true;
+                    break;
+                case "-f":
+                    forceCompilation = true;
+                    break;
+                case "-m":
+                    compilerFilter = getNextArgRequired();
+                    break;
+                case "-r":
+                    compilationReason = getNextArgRequired();
+                    break;
+                case "--check-prof":
+                    checkProfilesRaw = getNextArgRequired();
+                    break;
+                case "--reset":
+                    forceCompilation = true;
+                    clearProfileData = true;
+                    compilationReason = "install";
+                    break;
+                case "--secondary-dex":
+                    secondaryDex = true;
+                    break;
+                case "--split":
+                    split = getNextArgRequired();
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        if (checkProfilesRaw != null) {
+            if ("true".equals(checkProfilesRaw)) {
+                checkProfiles = true;
+            } else if ("false".equals(checkProfilesRaw)) {
+                checkProfiles = false;
+            } else {
+                pw.println("Invalid value for \"--check-prof\". Expected \"true\" or \"false\".");
+                return 1;
+            }
+        }
+
+        if (compilerFilter != null && compilationReason != null) {
+            pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") " +
+                    "at the same time");
+            return 1;
+        }
+        if (compilerFilter == null && compilationReason == null) {
+            pw.println("Cannot run without any of compilation filter (\"-m\") and compilation " +
+                    "reason (\"-r\") at the same time");
+            return 1;
+        }
+
+        if (allPackages && split != null) {
+            pw.println("-a cannot be specified together with --split");
+            return 1;
+        }
+
+        if (secondaryDex && split != null) {
+            pw.println("--secondary-dex cannot be specified together with --split");
+            return 1;
+        }
+
+        String targetCompilerFilter;
+        if (compilerFilter != null) {
+            if (!DexFile.isValidCompilerFilter(compilerFilter)) {
+                pw.println("Error: \"" + compilerFilter +
+                        "\" is not a valid compilation filter.");
+                return 1;
+            }
+            targetCompilerFilter = compilerFilter;
+        } else {
+            int reason = -1;
+            for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
+                if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals(
+                        compilationReason)) {
+                    reason = i;
+                    break;
+                }
+            }
+            if (reason == -1) {
+                pw.println("Error: Unknown compilation reason: " + compilationReason);
+                return 1;
+            }
+            targetCompilerFilter =
+                    PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason);
+        }
+
+
+        List<String> packageNames = null;
+        if (allPackages) {
+            packageNames = mInterface.getAllPackages();
+        } else {
+            String packageName = getNextArg();
+            if (packageName == null) {
+                pw.println("Error: package name not specified");
+                return 1;
+            }
+            packageNames = Collections.singletonList(packageName);
+        }
+
+        List<String> failedPackages = new ArrayList<>();
+        for (String packageName : packageNames) {
+            if (clearProfileData) {
+                mInterface.clearApplicationProfileData(packageName);
+            }
+
+            boolean result = secondaryDex
+                    ? mInterface.performDexOptSecondary(packageName,
+                            targetCompilerFilter, forceCompilation)
+                    : mInterface.performDexOptMode(packageName,
+                            checkProfiles, targetCompilerFilter, forceCompilation,
+                            true /* bootComplete */, split);
+            if (!result) {
+                failedPackages.add(packageName);
+            }
+        }
+
+        if (failedPackages.isEmpty()) {
+            pw.println("Success");
+            return 0;
+        } else if (failedPackages.size() == 1) {
+            pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled");
+            return 1;
+        } else {
+            pw.print("Failure: the following packages could not be compiled: ");
+            boolean is_first = true;
+            for (String packageName : failedPackages) {
+                if (is_first) {
+                    is_first = false;
+                } else {
+                    pw.print(", ");
+                }
+                pw.print(packageName);
+            }
+            pw.println();
+            return 1;
+        }
+    }
+
+    private int runreconcileSecondaryDexFiles() throws RemoteException {
+        String packageName = getNextArg();
+        mInterface.reconcileSecondaryDexFiles(packageName);
+        return 0;
+    }
+
+    public int runForceDexOpt() throws RemoteException {
+        mInterface.forceDexOpt(getNextArgRequired());
+        return 0;
+    }
+
+    private int runDexoptJob() throws RemoteException {
+        boolean result = mInterface.runBackgroundDexoptJob();
+        return result ? 0 : -1;
+    }
+
+    private int runDumpProfiles() throws RemoteException {
+        String packageName = getNextArg();
+        mInterface.dumpProfiles(packageName);
+        return 0;
+    }
+
+    private int runUninstall() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        int flags = 0;
+        int userId = UserHandle.USER_ALL;
+        int versionCode = PackageManager.VERSION_CODE_HIGHEST;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-k":
+                    flags |= PackageManager.DELETE_KEEP_DATA;
+                    break;
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                case "--versionCode":
+                    versionCode = Integer.parseInt(getNextArgRequired());
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        final String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+
+        // if a split is specified, just remove it and not the whole package
+        final String splitName = getNextArg();
+        if (splitName != null) {
+            return runRemoveSplit(packageName, splitName);
+        }
+
+        userId = translateUserId(userId, "runUninstall");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+            flags |= PackageManager.DELETE_ALL_USERS;
+        } else {
+            final PackageInfo info = mInterface.getPackageInfo(packageName,
+                    PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId);
+            if (info == null) {
+                pw.println("Failure [not installed for " + userId + "]");
+                return 1;
+            }
+            final boolean isSystem =
+                    (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+            // If we are being asked to delete a system app for just one
+            // user set flag so it disables rather than reverting to system
+            // version of the app.
+            if (isSystem) {
+                flags |= PackageManager.DELETE_SYSTEM_APP;
+            }
+        }
+
+        final LocalIntentReceiver receiver = new LocalIntentReceiver();
+        mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName,
+                versionCode), null /*callerPackageName*/, flags,
+                receiver.getIntentSender(), userId);
+
+        final Intent result = receiver.getResult();
+        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                PackageInstaller.STATUS_FAILURE);
+        if (status == PackageInstaller.STATUS_SUCCESS) {
+            pw.println("Success");
+            return 0;
+        } else {
+            pw.println("Failure ["
+                    + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+            return 1;
+        }
+    }
+
+    private int runRemoveSplit(String packageName, String splitName) throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final SessionParams sessionParams = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
+        sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+        sessionParams.appPackageName = packageName;
+        final int sessionId =
+                doCreateSession(sessionParams, null /*installerPackageName*/, UserHandle.USER_ALL);
+        boolean abandonSession = true;
+        try {
+            if (doRemoveSplit(sessionId, splitName, false /*logSuccess*/)
+                    != PackageInstaller.STATUS_SUCCESS) {
+                return 1;
+            }
+            if (doCommitSession(sessionId, false /*logSuccess*/)
+                    != PackageInstaller.STATUS_SUCCESS) {
+                return 1;
+            }
+            abandonSession = false;
+            pw.println("Success");
+            return 0;
+        } finally {
+            if (abandonSession) {
+                try {
+                    doAbandonSession(sessionId, false /*logSuccess*/);
+                } catch (Exception ignore) {
+                }
+            }
+        }
+    }
+
+    static class ClearDataObserver extends IPackageDataObserver.Stub {
+        boolean finished;
+        boolean result;
+
+        @Override
+        public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
+            synchronized (this) {
+                finished = true;
+                result = succeeded;
+                notifyAll();
+            }
+        }
+    }
+
+    private int runClear() throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+        String option = getNextOption();
+        if (option != null && option.equals("--user")) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
+        }
+
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified");
+            return 1;
+        }
+
+        ClearDataObserver obs = new ClearDataObserver();
+        ActivityManager.getService().clearApplicationUserData(pkg, obs, userId);
+        synchronized (obs) {
+            while (!obs.finished) {
+                try {
+                    obs.wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        if (obs.result) {
+            getOutPrintWriter().println("Success");
+            return 0;
+        } else {
+            getErrPrintWriter().println("Failed");
+            return 1;
+        }
+    }
+
+    private static String enabledSettingToString(int state) {
+        switch (state) {
+            case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
+                return "default";
+            case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
+                return "enabled";
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
+                return "disabled";
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
+                return "disabled-user";
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+                return "disabled-until-used";
+        }
+        return "unknown";
+    }
+
+    private int runSetEnabledSetting(int state) throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+        String option = getNextOption();
+        if (option != null && option.equals("--user")) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
+        }
+
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package or component specified");
+            return 1;
+        }
+        ComponentName cn = ComponentName.unflattenFromString(pkg);
+        if (cn == null) {
+            mInterface.setApplicationEnabledSetting(pkg, state, 0, userId,
+                    "shell:" + android.os.Process.myUid());
+            getOutPrintWriter().println("Package " + pkg + " new state: "
+                    + enabledSettingToString(
+                    mInterface.getApplicationEnabledSetting(pkg, userId)));
+            return 0;
+        } else {
+            mInterface.setComponentEnabledSetting(cn, state, 0, userId);
+            getOutPrintWriter().println("Component " + cn.toShortString() + " new state: "
+                    + enabledSettingToString(
+                    mInterface.getComponentEnabledSetting(cn, userId)));
+            return 0;
+        }
+    }
+
+    private int runSetHiddenSetting(boolean state) throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+        String option = getNextOption();
+        if (option != null && option.equals("--user")) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
+        }
+
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package or component specified");
+            return 1;
+        }
+        mInterface.setApplicationHiddenSettingAsUser(pkg, state, userId);
+        getOutPrintWriter().println("Package " + pkg + " new hidden state: "
+                + mInterface.getApplicationHiddenSettingAsUser(pkg, userId));
+        return 0;
+    }
+
+    private int runSuspend(boolean suspendedState) {
+        final PrintWriter pw = getOutPrintWriter();
+        int userId = UserHandle.USER_SYSTEM;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+
+        try {
+            mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
+                    userId);
+            pw.println("Package " + packageName + " new suspended state: "
+                    + mInterface.isPackageSuspendedForUser(packageName, userId));
+            return 0;
+        } catch (RemoteException | IllegalArgumentException e) {
+            pw.println(e.toString());
+            return 1;
+        }
+    }
+
+    private int runGrantRevokePermission(boolean grant) throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt = null;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            }
+        }
+
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified");
+            return 1;
+        }
+        String perm = getNextArg();
+        if (perm == null) {
+            getErrPrintWriter().println("Error: no permission specified");
+            return 1;
+        }
+
+        if (grant) {
+            mInterface.grantRuntimePermission(pkg, perm, userId);
+        } else {
+            mInterface.revokeRuntimePermission(pkg, perm, userId);
+        }
+        return 0;
+    }
+
+    private int runResetPermissions() throws RemoteException {
+        mInterface.resetRuntimePermissions();
+        return 0;
+    }
+
+    private int runSetPermissionEnforced() throws RemoteException {
+        final String permission = getNextArg();
+        if (permission == null) {
+            getErrPrintWriter().println("Error: no permission specified");
+            return 1;
+        }
+        final String enforcedRaw = getNextArg();
+        if (enforcedRaw == null) {
+            getErrPrintWriter().println("Error: no enforcement specified");
+            return 1;
+        }
+        mInterface.setPermissionEnforced(permission, Boolean.parseBoolean(enforcedRaw));
+        return 0;
+    }
+
+    private int runGetPrivappPermissions() {
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+        ArraySet<String> privAppPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg);
+        getOutPrintWriter().println(privAppPermissions == null
+                ? "{}" : privAppPermissions.toString());
+        return 0;
+    }
+
+    private int runGetPrivappDenyPermissions() {
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+        ArraySet<String> privAppDenyPermissions =
+                SystemConfig.getInstance().getPrivAppDenyPermissions(pkg);
+        getOutPrintWriter().println(privAppDenyPermissions == null
+                ? "{}" : privAppDenyPermissions.toString());
+        return 0;
+    }
+
+    private int runGetOemPermissions() {
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+        final Map<String, Boolean> oemPermissions = SystemConfig.getInstance()
+                .getOemPermissions(pkg);
+        if (oemPermissions == null || oemPermissions.isEmpty()) {
+            getOutPrintWriter().println("{}");
+        } else {
+            oemPermissions.forEach((permission, granted) ->
+                    getOutPrintWriter().println(permission + " granted:" + granted)
+            );
+        }
+        return 0;
+    }
+
+    private String linkStateToString(int state) {
+        switch (state) {
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined";
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask";
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always";
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never";
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask";
+        }
+        return "Unknown link state: " + state;
+    }
+
+    // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined}
+    private int runSetAppLink() throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: unknown option: " + opt);
+                return 1;
+            }
+        }
+
+        // Package name to act on; required
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+
+        // State to apply; {always|ask|never|undefined}, required
+        final String modeString = getNextArg();
+        if (modeString == null) {
+            getErrPrintWriter().println("Error: no app link state specified.");
+            return 1;
+        }
+
+        final int newMode;
+        switch (modeString.toLowerCase()) {
+            case "undefined":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+                break;
+
+            case "always":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+                break;
+
+            case "ask":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+                break;
+
+            case "always-ask":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
+                break;
+
+            case "never":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+                break;
+
+            default:
+                getErrPrintWriter().println("Error: unknown app link state '" + modeString + "'");
+                return 1;
+        }
+
+        final PackageInfo info = mInterface.getPackageInfo(pkg, 0, userId);
+        if (info == null) {
+            getErrPrintWriter().println("Error: package " + pkg + " not found.");
+            return 1;
+        }
+
+        if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
+            getErrPrintWriter().println("Error: package " + pkg + " does not handle web links.");
+            return 1;
+        }
+
+        if (!mInterface.updateIntentVerificationStatus(pkg, newMode, userId)) {
+            getErrPrintWriter().println("Error: unable to update app link status for " + pkg);
+            return 1;
+        }
+
+        return 0;
+    }
+
+    // pm get-app-link [--user USER_ID] PACKAGE
+    private int runGetAppLink() throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: unknown option: " + opt);
+                return 1;
+            }
+        }
+
+        // Package name to act on; required
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+
+        final PackageInfo info = mInterface.getPackageInfo(pkg, 0, userId);
+        if (info == null) {
+            getErrPrintWriter().println("Error: package " + pkg + " not found.");
+            return 1;
+        }
+
+        if ((info.applicationInfo.privateFlags
+                & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
+            getErrPrintWriter().println("Error: package " + pkg + " does not handle web links.");
+            return 1;
+        }
+
+        getOutPrintWriter().println(linkStateToString(
+                mInterface.getIntentVerificationStatus(pkg, userId)));
+
+        return 0;
+    }
+
+    private int runTrimCaches() throws RemoteException {
+        String size = getNextArg();
+        if (size == null) {
+            getErrPrintWriter().println("Error: no size specified");
+            return 1;
+        }
+        long multiplier = 1;
+        int len = size.length();
+        char c = size.charAt(len - 1);
+        if (c < '0' || c > '9') {
+            if (c == 'K' || c == 'k') {
+                multiplier = 1024L;
+            } else if (c == 'M' || c == 'm') {
+                multiplier = 1024L*1024L;
+            } else if (c == 'G' || c == 'g') {
+                multiplier = 1024L*1024L*1024L;
+            } else {
+                getErrPrintWriter().println("Invalid suffix: " + c);
+                return 1;
+            }
+            size = size.substring(0, len-1);
+        }
+        long sizeVal;
+        try {
+            sizeVal = Long.parseLong(size) * multiplier;
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: expected number at: " + size);
+            return 1;
+        }
+        String volumeUuid = getNextArg();
+        if ("internal".equals(volumeUuid)) {
+            volumeUuid = null;
+        }
+        ClearDataObserver obs = new ClearDataObserver();
+        mInterface.freeStorageAndNotify(volumeUuid, sizeVal,
+                StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED, obs);
+        synchronized (obs) {
+            while (!obs.finished) {
+                try {
+                    obs.wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+        return 0;
+    }
+
+    private static boolean isNumber(String s) {
+        try {
+            Integer.parseInt(s);
+        } catch (NumberFormatException nfe) {
+            return false;
+        }
+        return true;
+    }
+
+    public int runCreateUser() throws RemoteException {
+        String name;
+        int userId = -1;
+        int flags = 0;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if ("--profileOf".equals(opt)) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else if ("--managed".equals(opt)) {
+                flags |= UserInfo.FLAG_MANAGED_PROFILE;
+            } else if ("--restricted".equals(opt)) {
+                flags |= UserInfo.FLAG_RESTRICTED;
+            } else if ("--ephemeral".equals(opt)) {
+                flags |= UserInfo.FLAG_EPHEMERAL;
+            } else if ("--guest".equals(opt)) {
+                flags |= UserInfo.FLAG_GUEST;
+            } else if ("--demo".equals(opt)) {
+                flags |= UserInfo.FLAG_DEMO;
+            } else {
+                getErrPrintWriter().println("Error: unknown option " + opt);
+                return 1;
+            }
+        }
+        String arg = getNextArg();
+        if (arg == null) {
+            getErrPrintWriter().println("Error: no user name specified.");
+            return 1;
+        }
+        name = arg;
+        UserInfo info;
+        IUserManager um = IUserManager.Stub.asInterface(
+                ServiceManager.getService(Context.USER_SERVICE));
+        IAccountManager accm = IAccountManager.Stub.asInterface(
+                ServiceManager.getService(Context.ACCOUNT_SERVICE));
+        if ((flags & UserInfo.FLAG_RESTRICTED) != 0) {
+            // In non-split user mode, userId can only be SYSTEM
+            int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM;
+            info = um.createRestrictedProfile(name, parentUserId);
+            accm.addSharedAccountsFromParentUser(parentUserId, userId,
+                    (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell");
+        } else if (userId < 0) {
+            info = um.createUser(name, flags);
+        } else {
+            info = um.createProfileForUser(name, flags, userId, null);
+        }
+
+        if (info != null) {
+            getOutPrintWriter().println("Success: created user id " + info.id);
+            return 0;
+        } else {
+            getErrPrintWriter().println("Error: couldn't create User.");
+            return 1;
+        }
+    }
+
+    public int runRemoveUser() throws RemoteException {
+        int userId;
+        String arg = getNextArg();
+        if (arg == null) {
+            getErrPrintWriter().println("Error: no user id specified.");
+            return 1;
+        }
+        userId = UserHandle.parseUserArg(arg);
+        IUserManager um = IUserManager.Stub.asInterface(
+                ServiceManager.getService(Context.USER_SERVICE));
+        if (um.removeUser(userId)) {
+            getOutPrintWriter().println("Success: removed user");
+            return 0;
+        } else {
+            getErrPrintWriter().println("Error: couldn't remove user id " + userId);
+            return 1;
+        }
+    }
+
+    public int runSetUserRestriction() throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+        String opt = getNextOption();
+        if (opt != null && "--user".equals(opt)) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
+        }
+
+        String restriction = getNextArg();
+        String arg = getNextArg();
+        boolean value;
+        if ("1".equals(arg)) {
+            value = true;
+        } else if ("0".equals(arg)) {
+            value = false;
+        } else {
+            getErrPrintWriter().println("Error: valid value not specified");
+            return 1;
+        }
+        IUserManager um = IUserManager.Stub.asInterface(
+                ServiceManager.getService(Context.USER_SERVICE));
+        um.setUserRestriction(restriction, value, userId);
+        return 0;
+    }
+
+    public int runGetMaxUsers() {
+        getOutPrintWriter().println("Maximum supported users: "
+                + UserManager.getMaxSupportedUsers());
+        return 0;
+    }
+
     private static class InstallParams {
         SessionParams sessionParams;
         String installerPackageName;
@@ -1287,46 +1991,17 @@
         }
     }
 
-    private int runGetPrivappPermissions() {
-        final String pkg = getNextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
-            return 1;
-        }
-        ArraySet<String> privAppPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg);
-        getOutPrintWriter().println(privAppPermissions == null
-                ? "{}" : privAppPermissions.toString());
-        return 0;
-    }
+    private int runSetInstaller() throws RemoteException {
+        final String targetPackage = getNextArg();
+        final String installerPackageName = getNextArg();
 
-    private int runGetPrivappDenyPermissions() {
-        final String pkg = getNextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
+        if (targetPackage == null || installerPackageName == null) {
+            getErrPrintWriter().println("Must provide both target and installer package names");
             return 1;
         }
-        ArraySet<String> privAppDenyPermissions =
-                SystemConfig.getInstance().getPrivAppDenyPermissions(pkg);
-        getOutPrintWriter().println(privAppDenyPermissions == null
-                ? "{}" : privAppDenyPermissions.toString());
-        return 0;
-    }
 
-    private int runGetOemPermissions() {
-        final String pkg = getNextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
-            return 1;
-        }
-        final Map<String, Boolean> oemPermissions = SystemConfig.getInstance()
-                .getOemPermissions(pkg);
-        if (oemPermissions == null || oemPermissions.isEmpty()) {
-            getOutPrintWriter().println("{}");
-        } else {
-            oemPermissions.forEach((permission, granted) ->
-                getOutPrintWriter().println(permission + " granted:" + granted)
-            );
-        }
+        mInterface.setInstallerPackageName(targetPackage, installerPackageName);
+        getOutPrintWriter().println("Success");
         return 0;
     }
 
@@ -1367,6 +2042,16 @@
         }
     }
 
+    private int runDump() {
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified");
+            return 1;
+        }
+        ActivityManager.dumpPackageStateStatic(getOutFileDescriptor(), pkg);
+        return 0;
+    }
+
     private static String checkAbiArgument(String abi) {
         if (TextUtils.isEmpty(abi)) {
             throw new IllegalArgumentException("Missing ABI argument");
@@ -1663,10 +2348,203 @@
         pw.println("  help");
         pw.println("    Print this help text.");
         pw.println("");
+        pw.println("  path [--user USER_ID] PACKAGE");
+        pw.println("    Print the path to the .apk of the given PACKAGE.");
+        pw.println("");
+        pw.println("  dump PACKAGE");
+        pw.println("    Print various system state associated with the given PACKAGE.");
+        pw.println("");
+        pw.println("  list features");
+        pw.println("    Prints all features of the system.");
+        pw.println("");
+        pw.println("  has-feature FEATURE_NAME [version]");
+        pw.println("    Prints true and returns exit status 0 when system has a FEATURE_NAME,");
+        pw.println("    otherwise prints false and returns exit status 1");
+        pw.println("");
+        pw.println("  list instrumentation [-f] [TARGET-PACKAGE]");
+        pw.println("    Prints all test packages; optionally only those targeting TARGET-PACKAGE");
+        pw.println("    Options:");
+        pw.println("      -f: dump the name of the .apk file containing the test package");
+        pw.println("");
+        pw.println("  list libraries");
+        pw.println("    Prints all system libraries.");
+        pw.println("");
+        pw.println("  list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] ");
+        pw.println("      [--uid UID] [--user USER_ID] [FILTER]");
+        pw.println("    Prints all packages; optionally only those whose name contains");
+        pw.println("    the text in FILTER.  Options are:");
+        pw.println("      -f: see their associated file");
+        pw.println("      -d: filter to only show disabled 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("      -l: ignored (used for compatibility with older releases)");
+        pw.println("      -U: also show the package UID");
+        pw.println("      -u: also include uninstalled packages");
+        pw.println("      --uid UID: filter to only show packages with the given UID");
+        pw.println("      --user USER_ID: only list packages belonging to the given user");
+        pw.println("");
+        pw.println("  list permission-groups");
+        pw.println("    Prints all known permission groups.");
+        pw.println("");
+        pw.println("  list permissions [-g] [-f] [-d] [-u] [GROUP]");
+        pw.println("    Prints all known permissions; optionally only those in GROUP.  Options are:");
+        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("");
+        pw.println("  resolve-activity [--brief] [--components] [--user USER_ID] INTENT");
+        pw.println("    Prints the activity that resolves to the given INTENT.");
+        pw.println("");
+        pw.println("  query-activities [--brief] [--components] [--user USER_ID] INTENT");
+        pw.println("    Prints all activities that can handle the given INTENT.");
+        pw.println("");
+        pw.println("  query-services [--brief] [--components] [--user USER_ID] INTENT");
+        pw.println("    Prints all services that can handle the given INTENT.");
+        pw.println("");
+        pw.println("  query-receivers [--brief] [--components] [--user USER_ID] INTENT");
+        pw.println("    Prints all broadcast receivers that can handle the given INTENT.");
+        pw.println("");
+        pw.println("  install [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]");
+        pw.println("       [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
+        pw.println("       [--originating-uri URI] [---referrer URI]");
+        pw.println("       [--abi ABI_NAME] [--force-sdk]");
+        pw.println("       [--preload] [--instantapp] [--full] [--dont-kill]");
+        pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]");
+        pw.println("    Install an application.  Must provide the apk data to install, either as a");
+        pw.println("    file path or '-' to read from stdin.  Options are:");
+        pw.println("      -l: forward lock application");
+        pw.println("      -r: allow replacement of existing application");
+        pw.println("      -t: allow test packages");
+        pw.println("      -i: specify package name of installer owning the app");
+        pw.println("      -s: install application on sdcard");
+        pw.println("      -f: install application on internal flash");
+        pw.println("      -d: allow version code downgrade (debuggable packages only)");
+        pw.println("      -p: partial application install (new split on top of existing pkg)");
+        pw.println("      -g: grant all runtime permissions");
+        pw.println("      -S: size in bytes of package, required for stdin");
+        pw.println("      --user: install under the given user.");
+        pw.println("      --dont-kill: installing a new feature split, don't kill running app");
+        pw.println("      --originating-uri: set URI where app was downloaded from");
+        pw.println("      --referrer: set URI that instigated the install of the app");
+        pw.println("      --pkg: specify expected package name of app being installed");
+        pw.println("      --abi: override the default ABI of the platform");
+        pw.println("      --instantapp: cause the app to be installed as an ephemeral install app");
+        pw.println("      --full: cause the app to be installed as a non-ephemeral full app");
+        pw.println("      --install-location: force the install location:");
+        pw.println("          0=auto, 1=internal only, 2=prefer external");
+        pw.println("      --force-uuid: force install on to disk volume with given UUID");
+        pw.println("      --force-sdk: allow install even when existing app targets platform");
+        pw.println("          codename but new one targets a final API level");
+        pw.println("");
+        pw.println("  install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]");
+        pw.println("       [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
+        pw.println("       [--originating-uri URI] [---referrer URI]");
+        pw.println("       [--abi ABI_NAME] [--force-sdk]");
+        pw.println("       [--preload] [--instantapp] [--full] [--dont-kill]");
+        pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
+        pw.println("    Like \"install\", but starts an install session.  Use \"install-write\"");
+        pw.println("    to push data into the session, and \"install-commit\" to finish.");
+        pw.println("");
+        pw.println("  install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]");
+        pw.println("    Write an apk into the given install session.  If the path is '-', data");
+        pw.println("    will be read from stdin.  Options are:");
+        pw.println("      -S: size in bytes of package, required for stdin");
+        pw.println("");
+        pw.println("  install-commit SESSION_ID");
+        pw.println("    Commit the given active install session, installing the app.");
+        pw.println("");
+        pw.println("  install-abandon SESSION_ID");
+        pw.println("    Delete the given active install session.");
+        pw.println("");
+        pw.println("  set-install-location LOCATION");
+        pw.println("    Changes the default install location.  NOTE this is only intended for debugging;");
+        pw.println("    using this can cause applications to break and other undersireable behavior.");
+        pw.println("    LOCATION is one of:");
+        pw.println("    0 [auto]: Let system decide the best location");
+        pw.println("    1 [internal]: Install on internal device storage");
+        pw.println("    2 [external]: Install on external media");
+        pw.println("");
+        pw.println("  get-install-location");
+        pw.println("    Returns the current install location: 0, 1 or 2 as per set-install-location.");
+        pw.println("");
+        pw.println("  move-package PACKAGE [internal|UUID]");
+        pw.println("");
+        pw.println("  move-primary-storage [internal|UUID]");
+        pw.println("");
+        pw.println("  pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE [SPLIT]");
+        pw.println("    Remove the given package name from the system.  May remove an entire app");
+        pw.println("    if no SPLIT name is specified, otherwise will remove only the split of the");
+        pw.println("    given app.  Options are:");
+        pw.println("      -k: keep the data and cache directories around after package removal.");
+        pw.println("      --user: remove the app from the given user.");
+        pw.println("      --versionCode: only uninstall if the app has the given version code.");
+        pw.println("");
+        pw.println("  clear [--user USER_ID] PACKAGE");
+        pw.println("    Deletes all data associated with a package.");
+        pw.println("");
+        pw.println("  enable [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  disable [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  disable-user [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  default-state [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("    These commands change the enabled state of a given package or");
+        pw.println("    component (written as \"package/class\").");
+        pw.println("");
+        pw.println("  hide [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("");
+        pw.println("  suspend [--user USER_ID] TARGET-PACKAGE");
+        pw.println("    Suspends the specified package (as user).");
+        pw.println("");
+        pw.println("  unsuspend [--user USER_ID] TARGET-PACKAGE");
+        pw.println("    Unsuspends the specified package (as user).");
+        pw.println("");
+        pw.println("  grant [--user USER_ID] PACKAGE PERMISSION");
+        pw.println("  revoke [--user USER_ID] PACKAGE PERMISSION");
+        pw.println("    These commands either grant or revoke permissions to apps.  The permissions");
+        pw.println("    must be declared as used in the app's manifest, be runtime permissions");
+        pw.println("    (protection level dangerous), and the app targeting SDK greater than Lollipop MR1.");
+        pw.println("");
+        pw.println("  reset-permissions");
+        pw.println("    Revert all runtime permissions to their default state.");
+        pw.println("");
+        pw.println("  set-permission-enforced PERMISSION [true|false]");
+        pw.println("");
+        pw.println("  get-privapp-permissions TARGET-PACKAGE");
+        pw.println("    Prints all privileged permissions for a package.");
+        pw.println("");
+        pw.println("  get-privapp-deny-permissions TARGET-PACKAGE");
+        pw.println("    Prints all privileged permissions that are denied for a package.");
+        pw.println("");
+        pw.println("  get-oem-permissions TARGET-PACKAGE");
+        pw.println("    Prints all OEM permissions for a package.");
+        pw.println("");
+        pw.println("  set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}");
+        pw.println("  get-app-link [--user USER_ID] PACKAGE");
+        pw.println("");
+        pw.println("  trim-caches DESIRED_FREE_SPACE [internal|UUID]");
+        pw.println("    Trim cache files to reach the given free space.");
+        pw.println("");
+        pw.println("  create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral]");
+        pw.println("      [--guest] USER_NAME");
+        pw.println("    Create a new user with the given USER_NAME, printing the new user identifier");
+        pw.println("    of the user.");
+        pw.println("");
+        pw.println("  remove-user USER_ID");
+        pw.println("    Remove the user with the given USER_IDENTIFIER, deleting all data");
+        pw.println("    associated with that user");
+        pw.println("");
+        pw.println("  set-user-restriction [--user USER_ID] RESTRICTION VALUE");
+        pw.println("");
+        pw.println("  get-max-users");
+        pw.println("");
         pw.println("  compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]");
         pw.println("          [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)");
-        pw.println("    Trigger compilation of TARGET-PACKAGE or all packages if \"-a\".");
-        pw.println("    Options:");
+        pw.println("    Trigger compilation of TARGET-PACKAGE or all packages if \"-a\".  Options are:");
         pw.println("      -a: compile all packages");
         pw.println("      -c: clear profile data before compiling");
         pw.println("      -f: force compilation even if not needed");
@@ -1690,72 +2568,32 @@
         pw.println("      --check-prof (true | false): look at profiles when doing dexopt?");
         pw.println("      --secondary-dex: compile app secondary dex files");
         pw.println("      --split SPLIT: compile only the given split name");
+        pw.println("");
+        pw.println("  force-dex-opt PACKAGE");
+        pw.println("    Force immediate execution of dex opt for the given PACKAGE.");
+        pw.println("");
         pw.println("  bg-dexopt-job");
         pw.println("    Execute the background optimizations immediately.");
         pw.println("    Note that the command only runs the background optimizer logic. It may");
         pw.println("    overlap with the actual job but the job scheduler will not be able to");
         pw.println("    cancel it. It will also run even if the device is not in the idle");
         pw.println("    maintenance mode.");
-        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 targeting 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] [-l] [-u] [-U] "
-                + "[--uid UID] [--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 disabled 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("      -l: ignored (used for compatibility with older releases)");
-        pw.println("      -U: also show the package UID");
-        pw.println("      -u: also include uninstalled packages");
-        pw.println("      --uid UID: filter to only show packages with the given UID");
-        pw.println("      --user USER_ID: only list packages belonging to the given user");
+        pw.println("");
         pw.println("  reconcile-secondary-dex-files TARGET-PACKAGE");
         pw.println("    Reconciles the package secondary dex files with the generated oat files.");
-        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("");
         pw.println("  dump-profiles TARGET-PACKAGE");
         pw.println("    Dumps method/class profile files to");
         pw.println("    /data/misc/profman/TARGET-PACKAGE.txt");
-        pw.println("  resolve-activity [--brief] [--components] [--user USER_ID] INTENT");
-        pw.println("    Prints the activity that resolves to the given Intent.");
-        pw.println("  query-activities [--brief] [--components] [--user USER_ID] INTENT");
-        pw.println("    Prints all activities that can handle the given Intent.");
-        pw.println("  query-services [--brief] [--components] [--user USER_ID] INTENT");
-        pw.println("    Prints all services that can handle the given Intent.");
-        pw.println("  query-receivers [--brief] [--components] [--user USER_ID] INTENT");
-        pw.println("    Prints all broadcast receivers that can handle the given Intent.");
-        pw.println("  suspend [--user USER_ID] TARGET-PACKAGE");
-        pw.println("    Suspends the specified package (as user).");
-        pw.println("  unsuspend [--user USER_ID] TARGET-PACKAGE");
-        pw.println("    Unsuspends the specified package (as user).");
+        pw.println("");
         pw.println("  set-home-activity [--user USER_ID] TARGET-COMPONENT");
-        pw.println("    set the default home activity (aka launcher).");
-        pw.println("  has-feature FEATURE_NAME [version]");
-        pw.println("   prints true and returns exit status 0 when system has a FEATURE_NAME,");
-        pw.println("   otherwise prints false and returns exit status 1");
-        pw.println("  get-privileged-permissions TARGET-PACKAGE");
-        pw.println("   prints all privileged permissions for a package.");
-        pw.println("  get-oem-permissions TARGET-PACKAGE");
-        pw.println("   prints all OEM permissions for a package.");
+        pw.println("    Set the default home activity (aka launcher).");
+        pw.println("");
+        pw.println("  set-installer PACKAGE INSTALLER");
+        pw.println("    Set installer package name");
+        pw.println("");
+        pw.println("  get-instantapp-resolver");
+        pw.println("    Return the name of the component that is the current instant app installer.");
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
     }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 83cb2db2..3b414e9 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -86,6 +86,10 @@
         return sharedUserId;
     }
 
+    public SharedUserSetting getSharedUser() {
+        return sharedUser;
+    }
+
     @Override
     public String toString() {
         return "PackageSetting{"
@@ -120,6 +124,14 @@
         return appId;
     }
 
+    public void setInstallPermissionsFixed(boolean fixed) {
+        installPermissionsFixed = fixed;
+    }
+
+    public boolean areInstallPermissionsFixed() {
+        return installPermissionsFixed;
+    }
+
     public boolean isPrivileged() {
         return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
     }
@@ -136,6 +148,10 @@
         return (pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0;
     }
 
+    public boolean isUpdatedSystem() {
+        return (pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+    }
+
     @Override
     public boolean isSharedUser() {
         return sharedUser != null;
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index e19e83f..a838768 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -24,6 +24,7 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageUserState;
+import android.content.pm.Signature;
 import android.service.pm.PackageProto;
 import android.util.ArraySet;
 import android.util.SparseArray;
@@ -57,7 +58,7 @@
     static final int PKG_INSTALL_COMPLETE = 1;
     static final int PKG_INSTALL_INCOMPLETE = 0;
 
-    final String name;
+    public final String name;
     final String realName;
 
     String parentPackageName;
@@ -231,6 +232,11 @@
     public boolean isSharedUser() {
         return false;
     }
+
+    public Signature[] getSignatures() {
+        return signatures.mSignatures;
+    }
+
     /**
      * Makes a shallow copy of the given package settings.
      *
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 191b43a..7077fd1 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -544,7 +544,7 @@
         }
         final PackageSetting dp = mDisabledSysPackages.get(name);
         // always make sure the system package code and resource paths dont change
-        if (dp == null && p.pkg != null && p.pkg.isSystemApp() && !p.pkg.isUpdatedSystemApp()) {
+        if (dp == null && p.pkg != null && p.pkg.isSystem() && !p.pkg.isUpdatedSystemApp()) {
             if((p.pkg != null) && (p.pkg.applicationInfo != null)) {
                 p.pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
             }
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 71d3202..8c86db6 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -32,6 +32,7 @@
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.Permission;
 import android.content.pm.PermissionInfo;
+import android.content.pm.Signature;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
@@ -129,6 +130,9 @@
     public PackageSettingBase getSourcePackageSetting() {
         return sourcePackageSetting;
     }
+    public Signature[] getSourceSignatures() {
+        return sourcePackageSetting.getSignatures();
+    }
     public int getType() {
         return type;
     }
@@ -277,8 +281,8 @@
         // Allow system apps to redefine non-system permissions
         if (bp != null && !Objects.equals(bp.sourcePackageName, p.info.packageName)) {
             final boolean currentOwnerIsSystem = (bp.perm != null
-                    && bp.perm.owner.isSystemApp());
-            if (p.owner.isSystemApp()) {
+                    && bp.perm.owner.isSystem());
+            if (p.owner.isSystem()) {
                 if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) {
                     // It's a built-in permission and no owner, take ownership now
                     bp.sourcePackageSetting = pkgSetting;
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 161efd3..533b261 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -985,7 +985,7 @@
 
     private PackageParser.Package getSystemPackage(String packageName) {
         PackageParser.Package pkg = getPackage(packageName);
-        if (pkg != null && pkg.isSystemApp()) {
+        if (pkg != null && pkg.isSystem()) {
             return !isSysComponentOrPersistentPlatformSignedPrivApp(pkg) ? pkg : null;
         }
         return null;
@@ -1094,7 +1094,7 @@
         if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {
             return true;
         }
-        if (!pkg.isPrivilegedApp()) {
+        if (!pkg.isPrivileged()) {
             return false;
         }
         final PackageParser.Package disabledPkg =
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java
index 9605fcc..60c118b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java
@@ -58,7 +58,7 @@
         }
         public void onInstallPermissionRevoked() {
         }
-        public void onPermissionUpdated(int userId) {
+        public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
         }
         public void onPermissionRemoved() {
         }
@@ -66,6 +66,10 @@
         }
     }
 
+    public abstract void systemReady();
+
+    public abstract boolean isPermissionsReviewRequired(PackageParser.Package pkg, int userId);
+
     public abstract void grantRuntimePermission(
             @NonNull String permName, @NonNull String packageName, boolean overridePolicy,
             int callingUid, int userId, @Nullable PermissionCallback callback);
@@ -79,9 +83,12 @@
     public abstract void revokeRuntimePermission(@NonNull String permName,
             @NonNull String packageName, boolean overridePolicy, int callingUid, int userId,
             @Nullable PermissionCallback callback);
-    public abstract int[] revokeUnusedSharedUserPermissions(@NonNull SharedUserSetting suSetting,
-            @NonNull int[] allUserIds);
 
+    public abstract void updatePermissions(@Nullable String packageName,
+            @Nullable PackageParser.Package pkg, boolean replaceGrant,
+            @NonNull Collection<PackageParser.Package> allPacakges, PermissionCallback callback);
+    public abstract void updateAllPermissions(@Nullable String volumeUuid, boolean sdkUpdated,
+            @NonNull Collection<PackageParser.Package> allPacakges, PermissionCallback callback);
 
     /**
      * Add all permissions in the given package.
@@ -97,11 +104,6 @@
     public abstract void removeDynamicPermission(@NonNull String permName, int callingUid,
             @Nullable PermissionCallback callback);
 
-    public abstract int updatePermissions(@Nullable String changingPkg,
-            @Nullable PackageParser.Package pkgInfo, int flags);
-    public abstract int updatePermissionTrees(@Nullable String changingPkg,
-            @Nullable PackageParser.Package pkgInfo, int flags);
-
     public abstract @Nullable String[] getAppOpPermissionPackages(@NonNull String permName);
 
     public abstract int getPermissionFlags(@NonNull String permName,
@@ -161,5 +163,4 @@
 
     /** HACK HACK methods to allow for partial migration of data to the PermissionManager class */
     public abstract @Nullable BasePermission getPermissionTEMP(@NonNull String permName);
-    public abstract @Nullable int[] getGlobalGidsTEMP();
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index a94a00a..41b1add 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -19,6 +19,12 @@
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
+import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
+import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS;
+import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -36,10 +42,13 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
+import android.os.storage.StorageManager;
 import android.os.storage.StorageManagerInternal;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -47,8 +56,10 @@
 import android.util.SparseArray;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.os.RoSystemProperties;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
@@ -72,6 +83,7 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -110,8 +122,19 @@
             Manifest.permission.READ_PHONE_NUMBERS,
             Manifest.permission.ANSWER_PHONE_CALLS);
 
-    /** Cap the size of permission trees that 3rd party apps can define */
-    private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768;     // characters of text
+    /** Permission grant: not grant the permission. */
+    private static final int GRANT_DENIED = 1;
+    /** Permission grant: grant the permission as an install permission. */
+    private static final int GRANT_INSTALL = 2;
+    /** Permission grant: grant the permission as a runtime one. */
+    private static final int GRANT_RUNTIME = 3;
+    /** Permission grant: grant as runtime a permission that was granted as an install time one. */
+    private static final int GRANT_UPGRADE = 4;
+
+    /** Cap the size of permission trees that 3rd party apps can define; in characters of text */
+    private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768;
+    /** Empty array to avoid allocations */
+    private static final int[] EMPTY_INT_ARRAY = new int[0];
 
     /** Lock to protect internal data access */
     private final Object mLock;
@@ -125,17 +148,29 @@
     /** Default permission policy to provide proper behaviour out-of-the-box */
     private final DefaultPermissionGrantPolicy mDefaultPermissionGrantPolicy;
 
-    // System configuration read by SystemConfig.
+    /**
+     * Built-in permissions. Read from system configuration files. Mapping is from
+     * UID to permission name.
+     */
     private final SparseArray<ArraySet<String>> mSystemPermissions;
-    private final int[] mGlobalGids;
 
-    /** Internal storage for permissions and related settings */
-    private final PermissionSettings mSettings;
+    /** Built-in group IDs given to all packages. Read from system configuration files. */
+    private final int[] mGlobalGids;
 
     private final HandlerThread mHandlerThread;
     private final Handler mHandler;
     private final Context mContext;
 
+    /** Internal storage for permissions and related settings */
+    @GuardedBy("mLock")
+    private final PermissionSettings mSettings;
+
+    @GuardedBy("mLock")
+    private ArraySet<String> mPrivappPermissionsViolations;
+
+    @GuardedBy("mLock")
+    private boolean mSystemReady;
+
     PermissionManagerService(Context context,
             @Nullable DefaultPermissionGrantedCallback defaultGrantCallback,
             @NonNull Object externalLock) {
@@ -408,7 +443,7 @@
                 if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
                     p.group = mSettings.mPermissionGroups.get(p.info.group);
                     // Warn for a permission in an unknown group.
-                    if (PackageManagerService.DEBUG_PERMISSIONS
+                    if (DEBUG_PERMISSIONS
                             && p.info.group != null && p.group == null) {
                         Slog.i(TAG, "Permission " + p.info.name + " from package "
                                 + p.info.packageName + " in an unknown group " + p.info.group);
@@ -440,7 +475,7 @@
             final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName);
             if (cur == null || isPackageUpdate) {
                 mSettings.mPermissionGroups.put(pg.info.name, pg);
-                if (chatty && PackageManagerService.DEBUG_PACKAGE_SCANNING) {
+                if (chatty && DEBUG_PACKAGE_SCANNING) {
                     if (r == null) {
                         r = new StringBuilder(256);
                     } else {
@@ -455,7 +490,7 @@
                 Slog.w(TAG, "Permission group " + pg.info.name + " from package "
                         + pg.info.packageName + " ignored: original from "
                         + cur.info.packageName);
-                if (chatty && PackageManagerService.DEBUG_PACKAGE_SCANNING) {
+                if (chatty && DEBUG_PACKAGE_SCANNING) {
                     if (r == null) {
                         r = new StringBuilder(256);
                     } else {
@@ -466,7 +501,7 @@
                 }
             }
         }
-        if (r != null && PackageManagerService.DEBUG_PACKAGE_SCANNING) {
+        if (r != null && DEBUG_PACKAGE_SCANNING) {
             Log.d(TAG, "  Permission Groups: " + r);
         }
 
@@ -484,7 +519,7 @@
                 }
                 if (bp != null && bp.isPermission(p)) {
                     bp.setPermission(null);
-                    if (PackageManagerService.DEBUG_REMOVE && chatty) {
+                    if (DEBUG_REMOVE && chatty) {
                         if (r == null) {
                             r = new StringBuilder(256);
                         } else {
@@ -502,7 +537,7 @@
                 }
             }
             if (r != null) {
-                if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
+                if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
             }
 
             N = pkg.requestedPermissions.size();
@@ -520,7 +555,7 @@
                 }
             }
             if (r != null) {
-                if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
+                if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
             }
         }
     }
@@ -583,6 +618,579 @@
         }
     }
 
+    private void grantPermissions(PackageParser.Package pkg, boolean replace,
+            String packageOfInterest, PermissionCallback callback) {
+        // IMPORTANT: There are two types of permissions: install and runtime.
+        // Install time permissions are granted when the app is installed to
+        // all device users and users added in the future. Runtime permissions
+        // are granted at runtime explicitly to specific users. Normal and signature
+        // protected permissions are install time permissions. Dangerous permissions
+        // are install permissions if the app's target SDK is Lollipop MR1 or older,
+        // otherwise they are runtime permissions. This function does not manage
+        // runtime permissions except for the case an app targeting Lollipop MR1
+        // being upgraded to target a newer SDK, in which case dangerous permissions
+        // are transformed from install time to runtime ones.
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        if (ps == null) {
+            return;
+        }
+        final boolean isLegacySystemApp = mPackageManagerInt.isLegacySystemApp(pkg);
+
+        final PermissionsState permissionsState = ps.getPermissionsState();
+        PermissionsState origPermissions = permissionsState;
+
+        final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
+
+        boolean runtimePermissionsRevoked = false;
+        int[] updatedUserIds = EMPTY_INT_ARRAY;
+
+        boolean changedInstallPermission = false;
+
+        if (replace) {
+            ps.setInstallPermissionsFixed(false);
+            if (!ps.isSharedUser()) {
+                origPermissions = new PermissionsState(permissionsState);
+                permissionsState.reset();
+            } else {
+                // We need to know only about runtime permission changes since the
+                // calling code always writes the install permissions state but
+                // the runtime ones are written only if changed. The only cases of
+                // changed runtime permissions here are promotion of an install to
+                // runtime and revocation of a runtime from a shared user.
+                synchronized (mLock) {
+                    updatedUserIds = revokeUnusedSharedUserPermissionsLocked(
+                            ps.getSharedUser(), UserManagerService.getInstance().getUserIds());
+                    if (!ArrayUtils.isEmpty(updatedUserIds)) {
+                        runtimePermissionsRevoked = true;
+                    }
+                }
+            }
+        }
+
+        permissionsState.setGlobalGids(mGlobalGids);
+
+        synchronized (mLock) {
+            final int N = pkg.requestedPermissions.size();
+            for (int i = 0; i < N; i++) {
+                final String permName = pkg.requestedPermissions.get(i);
+                final BasePermission bp = mSettings.getPermissionLocked(permName);
+                final boolean appSupportsRuntimePermissions =
+                        pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M;
+
+                if (DEBUG_INSTALL) {
+                    Log.i(TAG, "Package " + pkg.packageName + " checking " + permName + ": " + bp);
+                }
+
+                if (bp == null || bp.getSourcePackageSetting() == null) {
+                    if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
+                        if (DEBUG_PERMISSIONS) {
+                            Slog.i(TAG, "Unknown permission " + permName
+                                    + " in package " + pkg.packageName);
+                        }
+                    }
+                    continue;
+                }
+
+                // Limit ephemeral apps to ephemeral allowed permissions.
+                if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
+                    if (DEBUG_PERMISSIONS) {
+                        Log.i(TAG, "Denying non-ephemeral permission " + bp.getName()
+                                + " for package " + pkg.packageName);
+                    }
+                    continue;
+                }
+
+                if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
+                    if (DEBUG_PERMISSIONS) {
+                        Log.i(TAG, "Denying runtime-only permission " + bp.getName()
+                                + " for package " + pkg.packageName);
+                    }
+                    continue;
+                }
+
+                final String perm = bp.getName();
+                boolean allowedSig = false;
+                int grant = GRANT_DENIED;
+
+                // Keep track of app op permissions.
+                if (bp.isAppOp()) {
+                    mSettings.addAppOpPackage(perm, pkg.packageName);
+                }
+
+                if (bp.isNormal()) {
+                    // For all apps normal permissions are install time ones.
+                    grant = GRANT_INSTALL;
+                } else if (bp.isRuntime()) {
+                    // If a permission review is required for legacy apps we represent
+                    // their permissions as always granted runtime ones since we need
+                    // to keep the review required permission flag per user while an
+                    // install permission's state is shared across all users.
+                    if (!appSupportsRuntimePermissions && !mSettings.mPermissionReviewRequired) {
+                        // For legacy apps dangerous permissions are install time ones.
+                        grant = GRANT_INSTALL;
+                    } else if (origPermissions.hasInstallPermission(bp.getName())) {
+                        // For legacy apps that became modern, install becomes runtime.
+                        grant = GRANT_UPGRADE;
+                    } else if (isLegacySystemApp) {
+                        // For legacy system apps, install becomes runtime.
+                        // We cannot check hasInstallPermission() for system apps since those
+                        // permissions were granted implicitly and not persisted pre-M.
+                        grant = GRANT_UPGRADE;
+                    } else {
+                        // For modern apps keep runtime permissions unchanged.
+                        grant = GRANT_RUNTIME;
+                    }
+                } else if (bp.isSignature()) {
+                    // For all apps signature permissions are install time ones.
+                    allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
+                    if (allowedSig) {
+                        grant = GRANT_INSTALL;
+                    }
+                }
+
+                if (DEBUG_PERMISSIONS) {
+                    Slog.i(TAG, "Granting permission " + perm + " to package " + pkg.packageName);
+                }
+
+                if (grant != GRANT_DENIED) {
+                    if (!ps.isSystem() && ps.areInstallPermissionsFixed()) {
+                        // If this is an existing, non-system package, then
+                        // we can't add any new permissions to it.
+                        if (!allowedSig && !origPermissions.hasInstallPermission(perm)) {
+                            // Except...  if this is a permission that was added
+                            // to the platform (note: need to only do this when
+                            // updating the platform).
+                            if (!isNewPlatformPermissionForPackage(perm, pkg)) {
+                                grant = GRANT_DENIED;
+                            }
+                        }
+                    }
+
+                    switch (grant) {
+                        case GRANT_INSTALL: {
+                            // Revoke this as runtime permission to handle the case of
+                            // a runtime permission being downgraded to an install one.
+                            // Also in permission review mode we keep dangerous permissions
+                            // for legacy apps
+                            for (int userId : UserManagerService.getInstance().getUserIds()) {
+                                if (origPermissions.getRuntimePermissionState(
+                                        perm, userId) != null) {
+                                    // Revoke the runtime permission and clear the flags.
+                                    origPermissions.revokeRuntimePermission(bp, userId);
+                                    origPermissions.updatePermissionFlags(bp, userId,
+                                          PackageManager.MASK_PERMISSION_FLAGS, 0);
+                                    // If we revoked a permission permission, we have to write.
+                                    updatedUserIds = ArrayUtils.appendInt(
+                                            updatedUserIds, userId);
+                                }
+                            }
+                            // Grant an install permission.
+                            if (permissionsState.grantInstallPermission(bp) !=
+                                    PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                changedInstallPermission = true;
+                            }
+                        } break;
+
+                        case GRANT_RUNTIME: {
+                            // Grant previously granted runtime permissions.
+                            for (int userId : UserManagerService.getInstance().getUserIds()) {
+                                final PermissionState permissionState = origPermissions
+                                        .getRuntimePermissionState(perm, userId);
+                                int flags = permissionState != null
+                                        ? permissionState.getFlags() : 0;
+                                if (origPermissions.hasRuntimePermission(perm, userId)) {
+                                    // Don't propagate the permission in a permission review
+                                    // mode if the former was revoked, i.e. marked to not
+                                    // propagate on upgrade. Note that in a permission review
+                                    // mode install permissions are represented as constantly
+                                    // granted runtime ones since we need to keep a per user
+                                    // state associated with the permission. Also the revoke
+                                    // on upgrade flag is no longer applicable and is reset.
+                                    final boolean revokeOnUpgrade = (flags & PackageManager
+                                            .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
+                                    if (revokeOnUpgrade) {
+                                        flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+                                        // Since we changed the flags, we have to write.
+                                        updatedUserIds = ArrayUtils.appendInt(
+                                                updatedUserIds, userId);
+                                    }
+                                    if (!mSettings.mPermissionReviewRequired || !revokeOnUpgrade) {
+                                        if (permissionsState.grantRuntimePermission(bp, userId) ==
+                                                PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                            // If we cannot put the permission as it was,
+                                            // we have to write.
+                                            updatedUserIds = ArrayUtils.appendInt(
+                                                    updatedUserIds, userId);
+                                        }
+                                    }
+
+                                    // If the app supports runtime permissions no need for a review.
+                                    if (mSettings.mPermissionReviewRequired
+                                            && appSupportsRuntimePermissions
+                                            && (flags & PackageManager
+                                                    .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                                        flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+                                        // Since we changed the flags, we have to write.
+                                        updatedUserIds = ArrayUtils.appendInt(
+                                                updatedUserIds, userId);
+                                    }
+                                } else if (mSettings.mPermissionReviewRequired
+                                        && !appSupportsRuntimePermissions) {
+                                    // For legacy apps that need a permission review, every new
+                                    // runtime permission is granted but it is pending a review.
+                                    // We also need to review only platform defined runtime
+                                    // permissions as these are the only ones the platform knows
+                                    // how to disable the API to simulate revocation as legacy
+                                    // apps don't expect to run with revoked permissions.
+                                    if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) {
+                                        if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
+                                            flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
+                                            // We changed the flags, hence have to write.
+                                            updatedUserIds = ArrayUtils.appendInt(
+                                                    updatedUserIds, userId);
+                                        }
+                                    }
+                                    if (permissionsState.grantRuntimePermission(bp, userId)
+                                            != PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                        // We changed the permission, hence have to write.
+                                        updatedUserIds = ArrayUtils.appendInt(
+                                                updatedUserIds, userId);
+                                    }
+                                }
+                                // Propagate the permission flags.
+                                permissionsState.updatePermissionFlags(bp, userId, flags, flags);
+                            }
+                        } break;
+
+                        case GRANT_UPGRADE: {
+                            // Grant runtime permissions for a previously held install permission.
+                            final PermissionState permissionState = origPermissions
+                                    .getInstallPermissionState(perm);
+                            final int flags =
+                                    (permissionState != null) ? permissionState.getFlags() : 0;
+
+                            if (origPermissions.revokeInstallPermission(bp)
+                                    != PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                // We will be transferring the permission flags, so clear them.
+                                origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL,
+                                        PackageManager.MASK_PERMISSION_FLAGS, 0);
+                                changedInstallPermission = true;
+                            }
+
+                            // If the permission is not to be promoted to runtime we ignore it and
+                            // also its other flags as they are not applicable to install permissions.
+                            if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) {
+                                for (int userId : currentUserIds) {
+                                    if (permissionsState.grantRuntimePermission(bp, userId) !=
+                                            PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                        // Transfer the permission flags.
+                                        permissionsState.updatePermissionFlags(bp, userId,
+                                                flags, flags);
+                                        // If we granted the permission, we have to write.
+                                        updatedUserIds = ArrayUtils.appendInt(
+                                                updatedUserIds, userId);
+                                    }
+                                }
+                            }
+                        } break;
+
+                        default: {
+                            if (packageOfInterest == null
+                                    || packageOfInterest.equals(pkg.packageName)) {
+                                if (DEBUG_PERMISSIONS) {
+                                    Slog.i(TAG, "Not granting permission " + perm
+                                            + " to package " + pkg.packageName
+                                            + " because it was previously installed without");
+                                }
+                            }
+                        } break;
+                    }
+                } else {
+                    if (permissionsState.revokeInstallPermission(bp) !=
+                            PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                        // Also drop the permission flags.
+                        permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
+                                PackageManager.MASK_PERMISSION_FLAGS, 0);
+                        changedInstallPermission = true;
+                        Slog.i(TAG, "Un-granting permission " + perm
+                                + " from package " + pkg.packageName
+                                + " (protectionLevel=" + bp.getProtectionLevel()
+                                + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+                                + ")");
+                    } else if (bp.isAppOp()) {
+                        // Don't print warning for app op permissions, since it is fine for them
+                        // not to be granted, there is a UI for the user to decide.
+                        if (DEBUG_PERMISSIONS
+                                && (packageOfInterest == null
+                                        || packageOfInterest.equals(pkg.packageName))) {
+                            Slog.i(TAG, "Not granting permission " + perm
+                                    + " to package " + pkg.packageName
+                                    + " (protectionLevel=" + bp.getProtectionLevel()
+                                    + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+                                    + ")");
+                        }
+                    }
+                }
+            }
+
+            if ((changedInstallPermission || replace) && !ps.areInstallPermissionsFixed() &&
+                    !ps.isSystem() || ps.isUpdatedSystem()) {
+                // This is the first that we have heard about this package, so the
+                // permissions we have now selected are fixed until explicitly
+                // changed.
+                ps.setInstallPermissionsFixed(true);
+            }
+        }
+
+        // Persist the runtime permissions state for users with changes. If permissions
+        // were revoked because no app in the shared user declares them we have to
+        // write synchronously to avoid losing runtime permissions state.
+        if (callback != null) {
+            callback.onPermissionUpdated(updatedUserIds, runtimePermissionsRevoked);
+        }
+    }
+
+    private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
+        boolean allowed = false;
+        final int NP = PackageParser.NEW_PERMISSIONS.length;
+        for (int ip=0; ip<NP; ip++) {
+            final PackageParser.NewPermissionInfo npi
+                    = PackageParser.NEW_PERMISSIONS[ip];
+            if (npi.name.equals(perm)
+                    && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) {
+                allowed = true;
+                Log.i(TAG, "Auto-granting " + perm + " to old pkg "
+                        + pkg.packageName);
+                break;
+            }
+        }
+        return allowed;
+    }
+
+    /**
+     * Determines whether a package is whitelisted for a particular privapp permission.
+     *
+     * <p>Does NOT check whether the package is a privapp, just whether it's whitelisted.
+     *
+     * <p>This handles parent/child apps.
+     */
+    private boolean hasPrivappWhitelistEntry(String perm, PackageParser.Package pkg) {
+        ArraySet<String> wlPermissions = SystemConfig.getInstance()
+                .getPrivAppPermissions(pkg.packageName);
+        // Let's check if this package is whitelisted...
+        boolean whitelisted = wlPermissions != null && wlPermissions.contains(perm);
+        // If it's not, we'll also tail-recurse to the parent.
+        return whitelisted ||
+                pkg.parentPackage != null && hasPrivappWhitelistEntry(perm, pkg.parentPackage);
+    }
+
+    private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
+            BasePermission bp, PermissionsState origPermissions) {
+        boolean oemPermission = bp.isOEM();
+        boolean privilegedPermission = bp.isPrivileged();
+        boolean privappPermissionsDisable =
+                RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE;
+        boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName());
+        boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName);
+        if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivileged()
+                && !platformPackage && platformPermission) {
+            if (!hasPrivappWhitelistEntry(perm, pkg)) {
+                Slog.w(TAG, "Privileged permission " + perm + " for package "
+                        + pkg.packageName + " - not in privapp-permissions whitelist");
+                // Only report violations for apps on system image
+                if (!mSystemReady && !pkg.isUpdatedSystemApp()) {
+                    // it's only a reportable violation if the permission isn't explicitly denied
+                    final ArraySet<String> deniedPermissions = SystemConfig.getInstance()
+                            .getPrivAppDenyPermissions(pkg.packageName);
+                    final boolean permissionViolation =
+                            deniedPermissions == null || !deniedPermissions.contains(perm);
+                    if (permissionViolation
+                            && RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+                        if (mPrivappPermissionsViolations == null) {
+                            mPrivappPermissionsViolations = new ArraySet<>();
+                        }
+                        mPrivappPermissionsViolations.add(pkg.packageName + ": " + perm);
+                    } else {
+                        return false;
+                    }
+                }
+                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+                    return false;
+                }
+            }
+        }
+        final String systemPackageName = mPackageManagerInt.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
+        final PackageParser.Package systemPackage =
+                mPackageManagerInt.getPackage(systemPackageName);
+        boolean allowed = (PackageManagerService.compareSignatures(
+                                bp.getSourceSignatures(), pkg.mSignatures)
+                        == PackageManager.SIGNATURE_MATCH)
+                || (PackageManagerService.compareSignatures(
+                                systemPackage.mSignatures, pkg.mSignatures)
+                        == PackageManager.SIGNATURE_MATCH);
+        if (!allowed && (privilegedPermission || oemPermission)) {
+            if (pkg.isSystem()) {
+                // For updated system applications, a privileged/oem permission
+                // is granted only if it had been defined by the original application.
+                if (pkg.isUpdatedSystemApp()) {
+                    final PackageParser.Package disabledPkg =
+                            mPackageManagerInt.getDisabledPackage(pkg.packageName);
+                    final PackageSetting disabledPs =
+                            (disabledPkg != null) ? (PackageSetting) disabledPkg.mExtras : null;
+                    if (disabledPs != null
+                            && disabledPs.getPermissionsState().hasInstallPermission(perm)) {
+                        // If the original was granted this permission, we take
+                        // that grant decision as read and propagate it to the
+                        // update.
+                        if ((privilegedPermission && disabledPs.isPrivileged())
+                                || (oemPermission && disabledPs.isOem()
+                                        && canGrantOemPermission(disabledPs, perm))) {
+                            allowed = true;
+                        }
+                    } else {
+                        // The system apk may have been updated with an older
+                        // version of the one on the data partition, but which
+                        // granted a new system permission that it didn't have
+                        // before.  In this case we do want to allow the app to
+                        // now get the new permission if the ancestral apk is
+                        // privileged to get it.
+                        if (disabledPkg != null
+                                && isPackageRequestingPermission(disabledPkg, perm)
+                                && ((privilegedPermission && disabledPs.isPrivileged())
+                                        || (oemPermission && disabledPs.isOem()
+                                                && canGrantOemPermission(disabledPs, perm)))) {
+                            allowed = true;
+                        }
+                        // Also if a privileged parent package on the system image or any of
+                        // its children requested a privileged/oem permission, the updated child
+                        // packages can also get the permission.
+                        if (pkg.parentPackage != null) {
+                            final PackageParser.Package disabledParentPkg = mPackageManagerInt
+                                    .getDisabledPackage(pkg.parentPackage.packageName);
+                            final PackageSetting disabledParentPs = (disabledParentPkg != null)
+                                    ? (PackageSetting) disabledParentPkg.mExtras : null;
+                            if (disabledParentPkg != null
+                                    && ((privilegedPermission && disabledParentPs.isPrivileged())
+                                            || (oemPermission && disabledParentPs.isOem()))) {
+                                if (isPackageRequestingPermission(disabledParentPkg, perm)
+                                        && canGrantOemPermission(disabledParentPs, perm)) {
+                                    allowed = true;
+                                } else if (disabledParentPkg.childPackages != null) {
+                                    for (PackageParser.Package disabledChildPkg
+                                            : disabledParentPkg.childPackages) {
+                                        final PackageSetting disabledChildPs =
+                                                (disabledChildPkg != null)
+                                                        ? (PackageSetting) disabledChildPkg.mExtras
+                                                        : null;
+                                        if (isPackageRequestingPermission(disabledChildPkg, perm)
+                                                && canGrantOemPermission(
+                                                        disabledChildPs, perm)) {
+                                            allowed = true;
+                                            break;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    final PackageSetting ps = (PackageSetting) pkg.mExtras;
+                    allowed = (privilegedPermission && pkg.isPrivileged())
+                            || (oemPermission && pkg.isOem()
+                                    && canGrantOemPermission(ps, perm));
+                }
+            }
+        }
+        if (!allowed) {
+            if (!allowed
+                    && bp.isPre23()
+                    && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+                // If this was a previously normal/dangerous permission that got moved
+                // to a system permission as part of the runtime permission redesign, then
+                // we still want to blindly grant it to old apps.
+                allowed = true;
+            }
+            if (!allowed && bp.isInstaller()
+                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+                            PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM))) {
+                // If this permission is to be granted to the system installer and
+                // this app is an installer, then it gets the permission.
+                allowed = true;
+            }
+            if (!allowed && bp.isVerifier()
+                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+                            PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM))) {
+                // If this permission is to be granted to the system verifier and
+                // this app is a verifier, then it gets the permission.
+                allowed = true;
+            }
+            if (!allowed && bp.isPreInstalled()
+                    && pkg.isSystem()) {
+                // Any pre-installed system app is allowed to get this permission.
+                allowed = true;
+            }
+            if (!allowed && bp.isDevelopment()) {
+                // For development permissions, a development permission
+                // is granted only if it was already granted.
+                allowed = origPermissions.hasInstallPermission(perm);
+            }
+            if (!allowed && bp.isSetup()
+                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+                            PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM))) {
+                // If this permission is to be granted to the system setup wizard and
+                // this app is a setup wizard, then it gets the permission.
+                allowed = true;
+            }
+        }
+        return allowed;
+    }
+
+    private static boolean canGrantOemPermission(PackageSetting ps, String permission) {
+        if (!ps.isOem()) {
+            return false;
+        }
+        // all oem permissions must explicitly be granted or denied
+        final Boolean granted =
+                SystemConfig.getInstance().getOemPermissions(ps.name).get(permission);
+        if (granted == null) {
+            throw new IllegalStateException("OEM permission" + permission + " requested by package "
+                    + ps.name + " must be explicitly declared granted or not");
+        }
+        return Boolean.TRUE == granted;
+    }
+
+    private boolean isPermissionsReviewRequired(PackageParser.Package pkg, int userId) {
+        if (!mSettings.mPermissionReviewRequired) {
+            return false;
+        }
+
+        // Permission review applies only to apps not supporting the new permission model.
+        if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
+            return false;
+        }
+
+        // Legacy apps have the permission and get user consent on launch.
+        if (pkg == null || pkg.mExtras == null) {
+            return false;
+        }
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+        return permissionsState.isPermissionReviewRequired(userId);
+    }
+
+    private boolean isPackageRequestingPermission(PackageParser.Package pkg, String permission) {
+        final int permCount = pkg.requestedPermissions.size();
+        for (int j = 0; j < permCount; j++) {
+            String requestedPermission = pkg.requestedPermissions.get(j);
+            if (permission.equals(requestedPermission)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void grantRuntimePermissionsGrantedToDisabledPackageLocked(
             PackageParser.Package pkg, int callingUid, PermissionCallback callback) {
         if (pkg.parentPackage == null) {
@@ -872,7 +1480,8 @@
         }
     }
 
-    private int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting, int[] allUserIds) {
+    private int[] revokeUnusedSharedUserPermissionsLocked(
+            SharedUserSetting suSetting, int[] allUserIds) {
         // Collect all used permissions in the UID
         final ArraySet<String> usedPermissions = new ArraySet<>();
         final List<PackageParser.Package> pkgList = suSetting.getPackages();
@@ -973,7 +1582,79 @@
         return permissionsState.getPermissionFlags(permName, userId);
     }
 
-    private int updatePermissions(String packageName, PackageParser.Package pkgInfo, int flags) {
+    private static final int UPDATE_PERMISSIONS_ALL = 1<<0;
+    private static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
+    private static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
+
+    private void updatePermissions(String packageName, PackageParser.Package pkg,
+            boolean replaceGrant, Collection<PackageParser.Package> allPackages,
+            PermissionCallback callback) {
+        final int flags = (pkg != null ? UPDATE_PERMISSIONS_ALL : 0) |
+                (replaceGrant ? UPDATE_PERMISSIONS_REPLACE_PKG : 0);
+        updatePermissions(
+                packageName, pkg, getVolumeUuidForPackage(pkg), flags, allPackages, callback);
+        if (pkg != null && pkg.childPackages != null) {
+            for (PackageParser.Package childPkg : pkg.childPackages) {
+                updatePermissions(childPkg.packageName, childPkg,
+                        getVolumeUuidForPackage(childPkg), flags, allPackages, callback);
+            }
+        }
+    }
+
+    private void updateAllPermissions(String volumeUuid, boolean sdkUpdated,
+            Collection<PackageParser.Package> allPackages, PermissionCallback callback) {
+        final int flags = UPDATE_PERMISSIONS_ALL |
+                (sdkUpdated
+                        ? UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL
+                        : 0);
+        updatePermissions(null, null, volumeUuid, flags, allPackages, callback);
+    }
+
+    private void updatePermissions(String changingPkgName, PackageParser.Package changingPkg,
+            String replaceVolumeUuid, int flags, Collection<PackageParser.Package> allPackages,
+            PermissionCallback callback) {
+        // TODO: Most of the methods exposing BasePermission internals [source package name,
+        // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't
+        // have package settings, we should make note of it elsewhere [map between
+        // source package name and BasePermission] and cycle through that here. Then we
+        // define a single method on BasePermission that takes a PackageSetting, changing
+        // package name and a package.
+        // NOTE: With this approach, we also don't need to tree trees differently than
+        // normal permissions. Today, we need two separate loops because these BasePermission
+        // objects are stored separately.
+        // Make sure there are no dangling permission trees.
+        flags = updatePermissionTrees(changingPkgName, changingPkg, flags);
+
+        // Make sure all dynamic permissions have been assigned to a package,
+        // and make sure there are no dangling permissions.
+        flags = updatePermissions(changingPkgName, changingPkg, flags);
+
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions");
+        // Now update the permissions for all packages, in particular
+        // replace the granted permissions of the system packages.
+        if ((flags & UPDATE_PERMISSIONS_ALL) != 0) {
+            for (PackageParser.Package pkg : allPackages) {
+                if (pkg != changingPkg) {
+                    // Only replace for packages on requested volume
+                    final String volumeUuid = getVolumeUuidForPackage(pkg);
+                    final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0)
+                            && Objects.equals(replaceVolumeUuid, volumeUuid);
+                    grantPermissions(pkg, replace, changingPkgName, callback);
+                }
+            }
+        }
+
+        if (changingPkg != null) {
+            // Only replace for packages on requested volume
+            final String volumeUuid = getVolumeUuidForPackage(changingPkg);
+            final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
+                    && Objects.equals(replaceVolumeUuid, volumeUuid);
+            grantPermissions(changingPkg, replace, changingPkgName, callback);
+        }
+        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+    }
+
+    private int updatePermissions(String packageName, PackageParser.Package pkg, int flags) {
         Set<BasePermission> needsUpdate = null;
         synchronized (mLock) {
             final Iterator<BasePermission> it = mSettings.mPermissions.values().iterator();
@@ -984,10 +1665,10 @@
                 }
                 if (bp.getSourcePackageSetting() != null) {
                     if (packageName != null && packageName.equals(bp.getSourcePackageName())
-                        && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) {
+                        && (pkg == null || !hasPermission(pkg, bp.getName()))) {
                         Slog.i(TAG, "Removing old permission tree: " + bp.getName()
                                 + " from package " + bp.getSourcePackageName());
-                        flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL;
+                        flags |= UPDATE_PERMISSIONS_ALL;
                         it.remove();
                     }
                     continue;
@@ -1000,13 +1681,13 @@
         }
         if (needsUpdate != null) {
             for (final BasePermission bp : needsUpdate) {
-                final PackageParser.Package pkg =
+                final PackageParser.Package sourcePkg =
                         mPackageManagerInt.getPackage(bp.getSourcePackageName());
                 synchronized (mLock) {
-                    if (pkg != null && pkg.mExtras != null) {
-                        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+                    if (sourcePkg != null && sourcePkg.mExtras != null) {
+                        final PackageSetting sourcePs = (PackageSetting) sourcePkg.mExtras;
                         if (bp.getSourcePackageSetting() == null) {
-                            bp.setSourcePackageSetting(ps);
+                            bp.setSourcePackageSetting(sourcePs);
                         }
                         continue;
                     }
@@ -1019,7 +1700,7 @@
         return flags;
     }
 
-    private int updatePermissionTrees(String packageName, PackageParser.Package pkgInfo,
+    private int updatePermissionTrees(String packageName, PackageParser.Package pkg,
             int flags) {
         Set<BasePermission> needsUpdate = null;
         synchronized (mLock) {
@@ -1028,10 +1709,10 @@
                 final BasePermission bp = it.next();
                 if (bp.getSourcePackageSetting() != null) {
                     if (packageName != null && packageName.equals(bp.getSourcePackageName())
-                        && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) {
+                        && (pkg == null || !hasPermission(pkg, bp.getName()))) {
                         Slog.i(TAG, "Removing old permission tree: " + bp.getName()
                                 + " from package " + bp.getSourcePackageName());
-                        flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL;
+                        flags |= UPDATE_PERMISSIONS_ALL;
                         it.remove();
                     }
                     continue;
@@ -1044,13 +1725,13 @@
         }
         if (needsUpdate != null) {
             for (final BasePermission bp : needsUpdate) {
-                final PackageParser.Package pkg =
+                final PackageParser.Package sourcePkg =
                         mPackageManagerInt.getPackage(bp.getSourcePackageName());
                 synchronized (mLock) {
-                    if (pkg != null && pkg.mExtras != null) {
-                        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+                    if (sourcePkg != null && sourcePkg.mExtras != null) {
+                        final PackageSetting sourcePs = (PackageSetting) sourcePkg.mExtras;
                         if (bp.getSourcePackageSetting() == null) {
-                            bp.setSourcePackageSetting(ps);
+                            bp.setSourcePackageSetting(sourcePs);
                         }
                         continue;
                     }
@@ -1113,7 +1794,7 @@
                 callback.onInstallPermissionUpdated();
             } else if (permissionsState.getRuntimePermissionState(permName, userId) != null
                     || hadState) {
-                callback.onPermissionUpdated(userId);
+                callback.onPermissionUpdated(new int[] { userId }, false);
             }
         }
     }
@@ -1211,6 +1892,29 @@
         }
     }
 
+    private void systemReady() {
+        mSystemReady = true;
+        if (mPrivappPermissionsViolations != null) {
+            throw new IllegalStateException("Signature|privileged permissions not in "
+                    + "privapp-permissions whitelist: " + mPrivappPermissionsViolations);
+        }
+    }
+
+    private static String getVolumeUuidForPackage(PackageParser.Package pkg) {
+        if (pkg == null) {
+            return StorageManager.UUID_PRIVATE_INTERNAL;
+        }
+        if (pkg.isExternal()) {
+            if (TextUtils.isEmpty(pkg.volumeUuid)) {
+                return StorageManager.UUID_PRIMARY_PHYSICAL;
+            } else {
+                return pkg.volumeUuid;
+            }
+        } else {
+            return StorageManager.UUID_PRIVATE_INTERNAL;
+        }
+    }
+
     private static boolean hasPermission(PackageParser.Package pkgInfo, String permName) {
         for (int i=pkgInfo.permissions.size()-1; i>=0; i--) {
             if (pkgInfo.permissions.get(i).info.name.equals(permName)) {
@@ -1282,6 +1986,14 @@
 
     private class PermissionManagerInternalImpl extends PermissionManagerInternal {
         @Override
+        public void systemReady() {
+            PermissionManagerService.this.systemReady();
+        }
+        @Override
+        public boolean isPermissionsReviewRequired(Package pkg, int userId) {
+            return PermissionManagerService.this.isPermissionsReviewRequired(pkg, userId);
+        }
+        @Override
         public void addAllPermissions(Package pkg, boolean chatty) {
             PermissionManagerService.this.addAllPermissions(pkg, chatty);
         }
@@ -1330,10 +2042,16 @@
                     overridePolicy, callingUid, userId, callback);
         }
         @Override
-        public int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting,
-                int[] allUserIds) {
-            return PermissionManagerService.this.revokeUnusedSharedUserPermissions(
-                    (SharedUserSetting) suSetting, allUserIds);
+        public void updatePermissions(String packageName, Package pkg, boolean replaceGrant,
+                Collection<PackageParser.Package> allPackages, PermissionCallback callback) {
+            PermissionManagerService.this.updatePermissions(
+                    packageName, pkg, replaceGrant, allPackages, callback);
+        }
+        @Override
+        public void updateAllPermissions(String volumeUuid, boolean sdkUpdated,
+                Collection<PackageParser.Package> allPackages, PermissionCallback callback) {
+            PermissionManagerService.this.updateAllPermissions(
+                    volumeUuid, sdkUpdated, allPackages, callback);
         }
         @Override
         public String[] getAppOpPermissionPackages(String permName) {
@@ -1346,16 +2064,6 @@
                     callingUid, userId);
         }
         @Override
-        public int updatePermissions(String packageName,
-                PackageParser.Package pkgInfo, int flags) {
-            return PermissionManagerService.this.updatePermissions(packageName, pkgInfo, flags);
-        }
-        @Override
-        public int updatePermissionTrees(String packageName,
-                PackageParser.Package pkgInfo, int flags) {
-            return PermissionManagerService.this.updatePermissionTrees(packageName, pkgInfo, flags);
-        }
-        @Override
         public void updatePermissionFlags(String permName, String packageName, int flagMask,
                 int flagValues, int callingUid, int userId, PermissionCallback callback) {
             PermissionManagerService.this.updatePermissionFlags(
@@ -1422,11 +2130,5 @@
                 return mSettings.getPermissionLocked(permName);
             }
         }
-        @Override
-        public int[] getGlobalGidsTEMP() {
-            synchronized (PermissionManagerService.this.mLock) {
-                return mGlobalGids;
-            }
-        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionSettings.java b/services/core/java/com/android/server/pm/permission/PermissionSettings.java
index 0ed94a1..f6c4990 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionSettings.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionSettings.java
@@ -47,7 +47,8 @@
  */
 public class PermissionSettings {
 
-    final boolean mPermissionReviewRequired;
+    public final boolean mPermissionReviewRequired;
+
     /**
      * All of the permissions known to the system. The mapping is from permission
      * name to permission object.
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 52526e2..5ce4d46 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -137,6 +137,8 @@
     float mLastDividerProgress;
     private final DividerSnapAlgorithm[] mSnapAlgorithmForRotation = new DividerSnapAlgorithm[4];
     private boolean mImeHideRequested;
+    private final Rect mLastDimLayerRect = new Rect();
+    private float mLastDimLayerAlpha;
 
     DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
@@ -525,7 +527,6 @@
      * display in that windowing mode.
      */
     void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
-        mService.openSurfaceTransaction();
         // TODO: Maybe only allow split-screen windowing modes?
         final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED
                 ? mDisplayContent.getStack(targetWindowingMode)
@@ -535,16 +536,33 @@
         if (visibleAndValid) {
             stack.getDimBounds(mTmpRect);
             if (mTmpRect.height() > 0 && mTmpRect.width() > 0) {
-                mDimLayer.setBounds(mTmpRect);
-                mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
+                if (!mLastDimLayerRect.equals(mTmpRect) || mLastDimLayerAlpha != alpha) {
+                    try {
+                        // TODO: This should use the regular animation transaction - here and below
+                        mService.openSurfaceTransaction();
+                        mDimLayer.setBounds(mTmpRect);
+                        mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
+                    } finally {
+                        mService.closeSurfaceTransaction();
+                    }
+                }
+                mLastDimLayerRect.set(mTmpRect);
+                mLastDimLayerAlpha = alpha;
             } else {
                 visibleAndValid = false;
             }
         }
         if (!visibleAndValid) {
-            mDimLayer.hide();
+            if (mLastDimLayerAlpha != 0f) {
+                try {
+                    mService.openSurfaceTransaction();
+                    mDimLayer.hide();
+                } finally {
+                    mService.closeSurfaceTransaction();
+                }
+            }
+            mLastDimLayerAlpha = 0f;
         }
-        mService.closeSurfaceTransaction();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index 1fda832..aff1bc6 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -152,7 +152,8 @@
         }
     }
 
-    public void positionChildAtBottom(TaskWindowContainerController child) {
+    public void positionChildAtBottom(TaskWindowContainerController child,
+            boolean includingParents) {
         if (child == null) {
             // TODO: Fix the call-points that cause this to happen.
             return;
@@ -164,7 +165,7 @@
                 Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found");
                 return;
             }
-            mContainer.positionChildAt(POSITION_BOTTOM, childTask, false /* includingParents */);
+            mContainer.positionChildAt(POSITION_BOTTOM, childTask, includingParents);
 
             if (mService.mAppTransition.isTransitionSet()) {
                 childTask.setSendingToBottom(true);
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index d170b6f..3320274 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1008,10 +1008,13 @@
     void resetAdjustedForIme(boolean adjustBoundsNow) {
         if (adjustBoundsNow) {
             mImeWin = null;
-            mAdjustedForIme = false;
             mImeGoingAway = false;
             mAdjustImeAmount = 0f;
             mAdjustDividerAmount = 0f;
+            if (!mAdjustedForIme) {
+                return;
+            }
+            mAdjustedForIme = false;
             updateAdjustedBounds();
             mService.setResizeDimLayer(false, getWindowingMode(), 1.0f);
         } else {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f0da474..5992a5f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7702,7 +7702,8 @@
     }
 
     boolean hasWideColorGamutSupport() {
-        return mHasWideColorGamutSupport;
+        return mHasWideColorGamutSupport &&
+                !SystemProperties.getBoolean("persist.sys.sf.native_mode", false);
     }
 
     void updateNonSystemOverlayWindowsVisibilityIfNeeded(WindowState win, boolean surfaceShown) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index d9db22e..b78fcdd 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1551,6 +1551,11 @@
             traceEnd();
         }
 
+        // Statsd helper
+        traceBeginAndSlog("StartStatsCompanionService");
+        mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
+        traceEnd();
+
         // Before things start rolling, be sure we have decided whether
         // we are in safe mode.
         final boolean safeMode = wm.detectSafeMode();
diff --git a/services/net/java/android/net/util/VersionedBroadcastListener.java b/services/net/java/android/net/util/VersionedBroadcastListener.java
new file mode 100644
index 0000000..115aa46
--- /dev/null
+++ b/services/net/java/android/net/util/VersionedBroadcastListener.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+
+/**
+ * A utility class that runs the provided callback on the provided handler when
+ * intents matching the provided filter arrive. Intents received by a stale
+ * receiver are safely ignored.
+ *
+ * Calls to startListening() and stopListening() must happen on the same thread.
+ *
+ * @hide
+ */
+public class VersionedBroadcastListener {
+    private static final boolean DBG = false;
+
+    public interface IntentCallback {
+        public void run(Intent intent);
+    }
+
+    private final String mTag;
+    private final Context mContext;
+    private final Handler mHandler;
+    private final IntentFilter mFilter;
+    private final Consumer<Intent> mCallback;
+    private final AtomicInteger mGenerationNumber;
+    private BroadcastReceiver mReceiver;
+
+    public VersionedBroadcastListener(String tag, Context ctx, Handler handler,
+            IntentFilter filter, Consumer<Intent> callback) {
+        mTag = tag;
+        mContext = ctx;
+        mHandler = handler;
+        mFilter = filter;
+        mCallback = callback;
+        mGenerationNumber = new AtomicInteger(0);
+    }
+
+    public int generationNumber() {
+        return mGenerationNumber.get();
+    }
+
+    public void startListening() {
+        if (DBG) Log.d(mTag, "startListening");
+        if (mReceiver != null) return;
+
+        mReceiver = new Receiver(mTag, mGenerationNumber, mCallback);
+        mContext.registerReceiver(mReceiver, mFilter, null, mHandler);
+    }
+
+    public void stopListening() {
+        if (DBG) Log.d(mTag, "stopListening");
+        if (mReceiver == null) return;
+
+        mGenerationNumber.incrementAndGet();
+        mContext.unregisterReceiver(mReceiver);
+        mReceiver = null;
+    }
+
+    private static class Receiver extends BroadcastReceiver {
+        public final String tag;
+        public final AtomicInteger atomicGenerationNumber;
+        public final Consumer<Intent> callback;
+        // Used to verify this receiver is still current.
+        public final int generationNumber;
+
+        public Receiver(
+                String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) {
+            this.tag = tag;
+            this.atomicGenerationNumber = atomicGenerationNumber;
+            this.callback = callback;
+            generationNumber = atomicGenerationNumber.incrementAndGet();
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final int currentGenerationNumber = atomicGenerationNumber.get();
+
+            if (DBG) {
+                Log.d(tag, "receiver generationNumber=" + generationNumber +
+                        ", current generationNumber=" + currentGenerationNumber);
+            }
+            if (generationNumber != currentGenerationNumber) return;
+
+            callback.accept(intent);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
index 60a55fa..7f03034 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
@@ -104,7 +104,7 @@
         // Move first activity to pinned stack.
         final Rect sourceBounds = new Rect();
         mSupervisor.moveActivityToPinnedStackLocked(firstActivity, sourceBounds,
-                0f /*aspectRatio*/, false, "initialMove");
+                0f /*aspectRatio*/, "initialMove");
 
         final ActivityDisplay display = mFullscreenStack.getDisplay();
         ActivityStack pinnedStack = display.getPinnedStack();
@@ -114,7 +114,7 @@
 
         // Move second activity to pinned stack.
         mSupervisor.moveActivityToPinnedStackLocked(secondActivity, sourceBounds,
-                0f /*aspectRatio*/, false, "secondMove");
+                0f /*aspectRatio*/, "secondMove");
 
         // Need to get stacks again as a new instance might have been created.
         pinnedStack = display.getPinnedStack();
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index f5cdf21..07b49c5 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -323,7 +323,7 @@
         }
 
         @Override
-        boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) {
+        boolean isStackTranslucent(ActivityRecord starting) {
             switch (mIsTranslucent) {
                 case IS_TRANSLUCENT_TRUE:
                     return true;
@@ -331,7 +331,7 @@
                     return false;
                 case IS_TRANSLUCENT_UNSET:
                 default:
-                    return super.isStackTranslucent(starting, stackBehind);
+                    return super.isStackTranslucent(starting);
             }
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java
new file mode 100644
index 0000000..0007e8a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import android.app.ActivityOptions;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Test;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.doAnswer;
+
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP;
+
+/**
+ * Tests for exercising resizing bounds due to activity options.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.am.LaunchingActivityPositionerTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LaunchingActivityPositionerTests extends ActivityTestsBase {
+    private final ComponentName testActivityComponent =
+            ComponentName.unflattenFromString("com.foo/.BarActivity");
+
+    private LaunchingActivityPositioner mPositioner;
+    private ActivityManagerService mService;
+    private ActivityStack mStack;
+    private TaskRecord mTask;
+    private ActivityRecord mActivity;
+
+    private Rect mCurrent;
+    private Rect mResult;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mService = createActivityManagerService();
+        mPositioner = new LaunchingActivityPositioner(mService.mStackSupervisor);
+        mCurrent = new Rect();
+        mResult = new Rect();
+
+
+        mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        mTask = createTask(mService.mStackSupervisor, testActivityComponent, mStack);
+        mActivity = createActivity(mService, testActivityComponent, mTask);
+    }
+
+
+    @Test
+    public void testSkippedInvocations() throws Exception {
+        // No specified activity should be ignored
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                null /*activity*/, null /*options*/, mCurrent, mResult));
+
+        // No specified activity options should be ignored
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, null /*options*/, mCurrent, mResult));
+
+        // launch bounds specified should be ignored.
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, options /*options*/, mCurrent, mResult));
+
+        // Non-resizeable records should be ignored
+        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+        assertFalse(mActivity.isResizeable());
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, options /*options*/, mCurrent, mResult));
+
+        // make record resizeable
+        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+        assertTrue(mActivity.isResizeable());
+
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, options /*options*/, mCurrent, mResult));
+
+        // Does not support freeform
+        mService.mSupportsFreeformWindowManagement = false;
+        assertFalse(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options));
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, options /*options*/, mCurrent, mResult));
+
+        mService.mSupportsFreeformWindowManagement = true;
+        options.setLaunchBounds(new Rect());
+        assertTrue(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options));
+
+        // Invalid bounds
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, options /*options*/, mCurrent, mResult));
+        options.setLaunchBounds(new Rect(0, 0, -1, -1));
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, options /*options*/, mCurrent, mResult));
+
+        // Valid bounds should cause the positioner to be applied.
+        options.setLaunchBounds(new Rect(0, 0, 100, 100));
+        assertEquals(RESULT_DONE, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, options /*options*/, mCurrent, mResult));
+    }
+
+    @Test
+    public void testBoundsExtraction() throws Exception {
+        // Make activity resizeable and enable freeform mode.
+        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+        mService.mSupportsFreeformWindowManagement = true;
+
+        ActivityOptions options = ActivityOptions.makeBasic();
+        final Rect proposedBounds = new Rect(20, 30, 45, 40);
+        options.setLaunchBounds(proposedBounds);
+
+        assertEquals(RESULT_DONE, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, options /*options*/, mCurrent, mResult));
+        assertEquals(mResult, proposedBounds);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java
new file mode 100644
index 0000000..f24a273
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for exercising {@link LaunchingBoundsController}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.am.LaunchingBoundsControllerTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LaunchingBoundsControllerTests extends ActivityTestsBase {
+    private LaunchingBoundsController mController;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mController = new LaunchingBoundsController();
+    }
+
+    /**
+     * Ensures positioners further down the chain are not called when RESULT_DONE is returned.
+     */
+    @Test
+    public void testEarlyExit() {
+        final LaunchingBoundsPositioner ignoredPositioner = mock(LaunchingBoundsPositioner.class);
+        final LaunchingBoundsPositioner earlyExitPositioner =
+                (task, layout, activity, options, current, result) -> RESULT_DONE;
+
+        mController.registerPositioner(ignoredPositioner);
+        mController.registerPositioner(earlyExitPositioner);
+
+        mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+                null /*options*/, new Rect());
+        verify(ignoredPositioner, never()).onCalculateBounds(any(), any(), any(), any(), any(),
+                any());
+    }
+
+    /**
+     * Ensures that positioners are called in the correct order.
+     */
+    @Test
+    public void testRegistration() {
+        LaunchingBoundsPositioner earlyExitPositioner =
+                new InstrumentedPositioner(RESULT_DONE, new Rect());
+
+        final LaunchingBoundsPositioner firstPositioner = spy(earlyExitPositioner);
+
+        mController.registerPositioner(firstPositioner);
+
+        mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+                null /*options*/, new Rect());
+        verify(firstPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(),
+                any());
+
+        final LaunchingBoundsPositioner secondPositioner = spy(earlyExitPositioner);
+
+        mController.registerPositioner(secondPositioner);
+
+        mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+                null /*options*/, new Rect());
+        verify(firstPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(),
+                any());
+        verify(secondPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(),
+                any());
+    }
+
+    /**
+     * Makes sure positioners further down the registration chain are called.
+     */
+    @Test
+    public void testPassThrough() {
+        final LaunchingBoundsPositioner positioner1 = mock(LaunchingBoundsPositioner.class);
+        final InstrumentedPositioner positioner2 = new InstrumentedPositioner(RESULT_CONTINUE,
+                new Rect (0, 0, 30, 20));
+
+        mController.registerPositioner(positioner1);
+        mController.registerPositioner(positioner2);
+
+        mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+                null /*options*/, new Rect());
+
+        verify(positioner1, times(1)).onCalculateBounds(any(), any(), any(), any(),
+                eq(positioner2.getLaunchBounds()), any());
+    }
+
+    /**
+     * Ensures skipped results are not propagated.
+     */
+    @Test
+    public void testSkip() {
+        final InstrumentedPositioner positioner1 =
+                new InstrumentedPositioner(RESULT_SKIP, new Rect(0, 0, 10, 10));
+
+
+        final InstrumentedPositioner positioner2 =
+                new InstrumentedPositioner(RESULT_CONTINUE, new Rect(0, 0, 20, 30));
+
+        mController.registerPositioner(positioner1);
+        mController.registerPositioner(positioner2);
+
+        final Rect resultBounds = new Rect();
+
+        mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+                null /*options*/, resultBounds);
+
+        assertEquals(resultBounds, positioner2.getLaunchBounds());
+    }
+
+    public static class InstrumentedPositioner implements LaunchingBoundsPositioner {
+        private int mReturnVal;
+        private Rect mBounds;
+        InstrumentedPositioner(int returnVal, Rect bounds) {
+            mReturnVal = returnVal;
+            mBounds = bounds;
+        }
+
+        @Override
+        public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout,
+                ActivityRecord activity, ActivityOptions options, Rect current, Rect result) {
+            result.set(mBounds);
+            return mReturnVal;
+        }
+
+        Rect getLaunchBounds() {
+            return mBounds;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingTaskPositionerTests.java
similarity index 81%
rename from services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java
rename to services/tests/servicestests/src/com/android/server/am/LaunchingTaskPositionerTests.java
index e6d6831..0d64981c 100644
--- a/services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/LaunchingTaskPositionerTests.java
@@ -17,15 +17,12 @@
 package com.android.server.am;
 
 import android.content.ComponentName;
-import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.WindowLayout;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import android.view.Display;
 import android.view.Gravity;
 import org.junit.runner.RunWith;
 import org.junit.Before;
@@ -33,10 +30,11 @@
 
 import org.mockito.invocation.InvocationOnMock;
 
-import java.util.ArrayList;
-
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE;
+
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -48,16 +46,14 @@
  * Tests for exercising resizing bounds.
  *
  * Build/Install/Run:
- *  bit FrameworksServicesTests:com.android.server.am.LaunchBoundsTests
+ *  bit FrameworksServicesTests:com.android.server.am.LaunchingTaskPositionerTests
  */
 @MediumTest
 @Presubmit
 @RunWith(AndroidJUnit4.class)
-public class LaunchBoundsTests extends ActivityTestsBase {
+public class LaunchingTaskPositionerTests extends ActivityTestsBase {
     private final ComponentName testActivityComponent =
             ComponentName.unflattenFromString("com.foo/.BarActivity");
-    private final ComponentName testActivityComponent2 =
-            ComponentName.unflattenFromString("com.foo/.BarActivity2");
 
     private final static int STACK_WIDTH = 100;
     private final static int STACK_HEIGHT = 200;
@@ -68,6 +64,11 @@
     private ActivityStack mStack;
     private TaskRecord mTask;
 
+    private LaunchingTaskPositioner mPositioner;
+
+    private Rect mCurrent;
+    private Rect mResult;
+
     @Before
     @Override
     public void setUp() throws Exception {
@@ -81,6 +82,11 @@
         // We must create the task after resizing to make sure it does not inherit the stack
         // dimensions on resize.
         mTask = createTask(mService.mStackSupervisor, testActivityComponent, mStack);
+
+        mPositioner = new LaunchingTaskPositioner();
+
+        mResult = new Rect();
+        mCurrent = new Rect();
     }
 
     /**
@@ -101,12 +107,9 @@
      */
     @Test
     public void testLaunchNoWindowLayout() throws Exception {
-        final Rect expectedTaskBounds = getDefaultBounds(Gravity.NO_GRAVITY);
-
-        mStack.layoutTaskInStack(mTask, null);
-
-        // We expect the task to be placed in the middle of the screen with margins applied.
-        assertEquals(mTask.mBounds, expectedTaskBounds);
+        assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask, null /*layout*/,
+                null /*record*/, null /*options*/, mCurrent, mResult));
+        assertEquals(getDefaultBounds(Gravity.NO_GRAVITY), mResult);
     }
 
     /**
@@ -116,11 +119,10 @@
      */
     @Test
     public void testlaunchEmptyWindowLayout() throws Exception {
-        final Rect expectedTaskBounds = getDefaultBounds(Gravity.NO_GRAVITY);
-
-        WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0);
-        mStack.layoutTaskInStack(mTask, layout);
-        assertEquals(mTask.mBounds, expectedTaskBounds);
+        assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask,
+                new WindowLayout(0, 0, 0, 0, Gravity.NO_GRAVITY, 0, 0), null /*activity*/,
+                null /*options*/, mCurrent, mResult));
+        assertEquals(mResult, getDefaultBounds(Gravity.NO_GRAVITY));
     }
 
     /**
@@ -149,9 +151,15 @@
     }
 
     private void testGravity(int gravity) {
-        final WindowLayout gravityLayout = new WindowLayout(0, 0, 0, 0, gravity, 0, 0);
-        mStack.layoutTaskInStack(mTask, gravityLayout);
-        assertEquals(mTask.mBounds, getDefaultBounds(gravity));
+        try {
+            assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask,
+                    new WindowLayout(0, 0, 0, 0, gravity, 0, 0), null /*activity*/,
+                    null /*options*/, mCurrent, mResult));
+            assertEquals(mResult, getDefaultBounds(gravity));
+        } finally {
+            mCurrent.setEmpty();
+            mResult.setEmpty();
+        }
     }
 
     /**
@@ -174,7 +182,7 @@
         final WindowLayout layout = new WindowLayout(0, 0, 0, 0, gravity, 0, 0);
 
         // layout first task
-        mStack.layoutTaskInStack(mTask, layout /*windowLayout*/);
+        mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(mTask, layout);
 
         // Second task will be laid out on top of the first so starting bounds is the same.
         final Rect expectedBounds = new Rect(mTask.mBounds);
@@ -192,7 +200,9 @@
                     mStack);
 
             // layout second task
-            mStack.layoutTaskInStack(secondTask, layout /*windowLayout*/);
+            assertEquals(RESULT_CONTINUE,
+                    mPositioner.onCalculateBounds(secondTask, layout, null /*activity*/,
+                            null /*options*/, mCurrent, mResult));
 
             if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT)
                     || (gravity & (Gravity.BOTTOM | Gravity.RIGHT))
@@ -207,7 +217,7 @@
                         LaunchingTaskPositioner.getVerticalStep(mStack.mBounds));
             }
 
-            assertEquals(secondTask.mBounds, expectedBounds);
+            assertEquals(mResult, expectedBounds);
         } finally {
             // Remove task and activity to prevent influencing future tests
             if (activity != null) {
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
index 42ddedf..67ffe58 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
@@ -16,6 +16,11 @@
 
 package com.android.server.usage;
 
+import static android.app.usage.AppStandby.REASON_TIMEOUT;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_ACTIVE;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_RARE;
+
+import android.app.usage.AppStandby;
 import android.os.FileUtils;
 import android.test.AndroidTestCase;
 
@@ -28,6 +33,8 @@
     final static String PACKAGE_1 = "com.android.testpackage1";
     final static String PACKAGE_2 = "com.android.testpackage2";
 
+    final static int USER_ID = 0;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -42,7 +49,6 @@
     }
 
     public void testFilesCreation() {
-        final int userId = 0;
         AppIdleHistory aih = new AppIdleHistory(mStorageDir, 0);
 
         aih.updateDisplay(true, /* elapsedRealtime= */ 1000);
@@ -50,9 +56,9 @@
         // Screen On time file should be written right away
         assertTrue(aih.getScreenOnTimeFile().exists());
 
-        aih.writeAppIdleTimes(userId);
+        aih.writeAppIdleTimes(USER_ID);
         // stats file should be written now
-        assertTrue(new File(new File(mStorageDir, "users/" + userId),
+        assertTrue(new File(new File(mStorageDir, "users/" + USER_ID),
                 AppIdleHistory.APP_IDLE_FILENAME).exists());
     }
 
@@ -77,24 +83,33 @@
         assertEquals(aih2.getScreenOnTime(13000), 4000);
     }
 
-    public void testPackageEvents() {
+    public void testBuckets() {
         AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000);
-        aih.setThresholds(4000, 1000);
-        aih.updateDisplay(true, 1000);
-        // App is not-idle by default
-        assertFalse(aih.isIdle(PACKAGE_1, 0, 1500));
-        // Still not idle
-        assertFalse(aih.isIdle(PACKAGE_1, 0, 3000));
-        // Idle now
-        assertTrue(aih.isIdle(PACKAGE_1, 0, 8000));
-        // Not idle
-        assertFalse(aih.isIdle(PACKAGE_2, 0, 9000));
 
-        // Screen off
-        aih.updateDisplay(false, 9100);
-        // Still idle after 10 seconds because screen hasn't been on long enough
-        assertFalse(aih.isIdle(PACKAGE_2, 0, 20000));
-        aih.updateDisplay(true, 21000);
-        assertTrue(aih.isIdle(PACKAGE_2, 0, 23000));
+        aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 1000, STANDBY_BUCKET_ACTIVE,
+                AppStandby.REASON_USAGE);
+        // ACTIVE means not idle
+        assertFalse(aih.isIdle(PACKAGE_1, USER_ID, 2000));
+
+        aih.setAppStandbyBucket(PACKAGE_2, USER_ID, 2000, STANDBY_BUCKET_ACTIVE,
+                AppStandby.REASON_USAGE);
+        aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 3000, STANDBY_BUCKET_RARE,
+                REASON_TIMEOUT);
+
+        assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 3000), STANDBY_BUCKET_RARE);
+        assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 3000), STANDBY_BUCKET_ACTIVE);
+        assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000), REASON_TIMEOUT);
+
+        // RARE is considered idle
+        assertTrue(aih.isIdle(PACKAGE_1, USER_ID, 3000));
+        assertFalse(aih.isIdle(PACKAGE_2, USER_ID, 3000));
+
+        // Check persistence
+        aih.writeAppIdleDurations();
+        aih.writeAppIdleTimes(USER_ID);
+        aih = new AppIdleHistory(mStorageDir, 4000);
+        assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 5000), STANDBY_BUCKET_RARE);
+        assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 5000), STANDBY_BUCKET_ACTIVE);
+        assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_TIMEOUT);
     }
 }
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
new file mode 100644
index 0000000..9846d6f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usage;
+
+import static android.app.usage.AppStandby.STANDBY_BUCKET_ACTIVE;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_FREQUENT;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_RARE;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_WORKING_SET;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.app.usage.AppStandby;
+import android.app.usage.UsageEvents;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Display;
+
+import com.android.server.SystemService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit test for AppStandbyController.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AppStandbyControllerTests {
+
+    private static final String PACKAGE_1 = "com.example.foo";
+    private static final int UID_1 = 10000;
+    private static final int USER_ID = 0;
+
+    private static final long MINUTE_MS = 60 * 1000;
+    private static final long HOUR_MS = 60 * MINUTE_MS;
+    private static final long DAY_MS = 24 * HOUR_MS;
+
+    private MyInjector mInjector;
+
+    static class MyContextWrapper extends ContextWrapper {
+        PackageManager mockPm = mock(PackageManager.class);
+
+        public MyContextWrapper(Context base) {
+            super(base);
+        }
+
+        public PackageManager getPackageManager() {
+            return mockPm;
+        }
+    }
+
+    static class MyInjector extends AppStandbyController.Injector {
+        long mElapsedRealtime;
+        boolean mIsCharging;
+        List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>();
+        boolean mDisplayOn;
+        DisplayManager.DisplayListener mDisplayListener;
+        String mBoundWidgetPackage;
+
+        MyInjector(Context context, Looper looper) {
+            super(context, looper);
+        }
+
+        @Override
+        void onBootPhase(int phase) {
+        }
+
+        @Override
+        int getBootPhase() {
+            return SystemService.PHASE_BOOT_COMPLETED;
+        }
+
+        @Override
+        long elapsedRealtime() {
+            return mElapsedRealtime;
+        }
+
+        @Override
+        long currentTimeMillis() {
+            return mElapsedRealtime;
+        }
+
+        @Override
+        boolean isAppIdleEnabled() {
+            return true;
+        }
+
+        @Override
+        boolean isCharging() {
+            return mIsCharging;
+        }
+
+        @Override
+        boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
+            return mPowerSaveWhitelistExceptIdle.contains(packageName);
+        }
+
+        @Override
+        File getDataSystemDirectory() {
+            return new File(getContext().getFilesDir(), Long.toString(Math.randomLongInternal()));
+        }
+
+        @Override
+        void noteEvent(int event, String packageName, int uid) throws RemoteException {
+        }
+
+        @Override
+        boolean isPackageEphemeral(int userId, String packageName) {
+            // TODO: update when testing ephemeral apps scenario
+            return false;
+        }
+
+        @Override
+        int[] getRunningUserIds() {
+            return new int[] {USER_ID};
+        }
+
+        @Override
+        boolean isDefaultDisplayOn() {
+            return mDisplayOn;
+        }
+
+        @Override
+        void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler) {
+            mDisplayListener = listener;
+        }
+
+        @Override
+        String getActiveNetworkScorer() {
+            return null;
+        }
+
+        @Override
+        public boolean isBoundWidgetPackage(AppWidgetManager appWidgetManager, String packageName,
+                int userId) {
+            return packageName != null && packageName.equals(mBoundWidgetPackage);
+        }
+
+        // Internal methods
+
+        void setDisplayOn(boolean on) {
+            mDisplayOn = on;
+            if (mDisplayListener != null) {
+                mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
+            }
+        }
+    }
+
+    private void setupPm(PackageManager mockPm) throws PackageManager.NameNotFoundException {
+        List<PackageInfo> packages = new ArrayList<>();
+        PackageInfo pi = new PackageInfo();
+        pi.applicationInfo = new ApplicationInfo();
+        pi.applicationInfo.uid = UID_1;
+        pi.packageName = PACKAGE_1;
+        packages.add(pi);
+
+        doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), anyInt());
+        try {
+            doReturn(UID_1).when(mockPm).getPackageUidAsUser(anyString(), anyInt(), anyInt());
+            doReturn(pi.applicationInfo).when(mockPm).getApplicationInfo(anyString(), anyInt());
+        } catch (PackageManager.NameNotFoundException nnfe) {}
+    }
+
+    private void setChargingState(AppStandbyController controller, boolean charging) {
+        mInjector.mIsCharging = charging;
+        if (controller != null) {
+            controller.setChargingState(charging);
+        }
+    }
+
+    private AppStandbyController setupController() throws Exception {
+        mInjector.mElapsedRealtime = 0;
+        AppStandbyController controller = new AppStandbyController(mInjector);
+        controller.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        controller.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+        mInjector.setDisplayOn(false);
+        mInjector.setDisplayOn(true);
+        setChargingState(controller, false);
+        setupPm(mInjector.getContext().getPackageManager());
+        controller.checkIdleStates(USER_ID);
+
+        return controller;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MyContextWrapper myContext = new MyContextWrapper(InstrumentationRegistry.getContext());
+        mInjector = new MyInjector(myContext, Looper.getMainLooper());
+    }
+
+    @Test
+    public void testCharging() throws Exception {
+        AppStandbyController controller = setupController();
+
+        setChargingState(controller, true);
+        mInjector.mElapsedRealtime = 8 * DAY_MS;
+        assertFalse(controller.isAppIdleFilteredOrParoled(PACKAGE_1, USER_ID,
+                mInjector.mElapsedRealtime, false));
+
+        setChargingState(controller, false);
+        mInjector.mElapsedRealtime = 16 * DAY_MS;
+        controller.checkIdleStates(USER_ID);
+        assertTrue(controller.isAppIdleFilteredOrParoled(PACKAGE_1, USER_ID,
+                mInjector.mElapsedRealtime, false));
+        setChargingState(controller, true);
+        assertFalse(controller.isAppIdleFilteredOrParoled(PACKAGE_1,USER_ID,
+                mInjector.mElapsedRealtime, false));
+    }
+
+    private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) {
+        mInjector.mElapsedRealtime = elapsedTime;
+        controller.checkIdleStates(USER_ID);
+        assertEquals(bucket,
+                controller.getAppStandbyBucket(PACKAGE_1, USER_ID, mInjector.mElapsedRealtime,
+                        false));
+    }
+
+    @Test
+    public void testBuckets() throws Exception {
+        AppStandbyController controller = setupController();
+
+        // ACTIVE bucket
+        assertTimeout(controller, 11 * HOUR_MS, STANDBY_BUCKET_ACTIVE);
+
+        // WORKING_SET bucket
+        assertTimeout(controller, 25 * HOUR_MS, STANDBY_BUCKET_WORKING_SET);
+
+        // WORKING_SET bucket
+        assertTimeout(controller, 47 * HOUR_MS, STANDBY_BUCKET_WORKING_SET);
+
+        // FREQUENT bucket
+        assertTimeout(controller, 4 * DAY_MS, STANDBY_BUCKET_FREQUENT);
+
+        // RARE bucket
+        assertTimeout(controller, 9 * DAY_MS, STANDBY_BUCKET_RARE);
+
+        // Back to ACTIVE on event
+        UsageEvents.Event ev = new UsageEvents.Event();
+        ev.mPackage = PACKAGE_1;
+        ev.mEventType = UsageEvents.Event.USER_INTERACTION;
+        controller.reportEvent(ev, mInjector.mElapsedRealtime, USER_ID);
+
+        assertTimeout(controller, 9 * DAY_MS, STANDBY_BUCKET_ACTIVE);
+
+        // RARE bucket
+        assertTimeout(controller, 18 * DAY_MS, STANDBY_BUCKET_RARE);
+    }
+
+    @Test
+    public void testScreenTimeAndBuckets() throws Exception {
+        AppStandbyController controller = setupController();
+        mInjector.setDisplayOn(false);
+
+        // ACTIVE bucket
+        assertTimeout(controller, 11 * HOUR_MS, STANDBY_BUCKET_ACTIVE);
+
+        // WORKING_SET bucket
+        assertTimeout(controller, 25 * HOUR_MS, STANDBY_BUCKET_WORKING_SET);
+
+        // RARE bucket, should fail because the screen wasn't ON.
+        mInjector.mElapsedRealtime = 9 * DAY_MS;
+        controller.checkIdleStates(USER_ID);
+        assertNotEquals(STANDBY_BUCKET_RARE,
+                controller.getAppStandbyBucket(PACKAGE_1, USER_ID, mInjector.mElapsedRealtime,
+                false));
+
+        mInjector.setDisplayOn(true);
+        assertTimeout(controller, 18 * DAY_MS, STANDBY_BUCKET_RARE);
+    }
+
+    @Test
+    public void testForcedIdle() throws Exception {
+        AppStandbyController controller = setupController();
+        setChargingState(controller, false);
+
+        controller.forceIdleState(PACKAGE_1, USER_ID, true);
+        assertEquals(STANDBY_BUCKET_RARE, controller.getAppStandbyBucket(PACKAGE_1, USER_ID, 0,
+                true));
+        assertTrue(controller.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+
+        controller.forceIdleState(PACKAGE_1, USER_ID, false);
+        assertEquals(STANDBY_BUCKET_ACTIVE, controller.getAppStandbyBucket(PACKAGE_1, USER_ID, 0,
+                true));
+        assertFalse(controller.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+    }
+}
diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java
index f298559..e5d3915 100644
--- a/services/usage/java/com/android/server/usage/AppIdleHistory.java
+++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java
@@ -16,6 +16,9 @@
 
 package com.android.server.usage;
 
+import static android.app.usage.AppStandby.*;
+
+import android.app.usage.AppStandby;
 import android.os.Environment;
 import android.os.SystemClock;
 import android.util.ArrayMap;
@@ -51,8 +54,10 @@
 
     private static final String TAG = "AppIdleHistory";
 
+    private static final boolean DEBUG = AppStandbyController.DEBUG;
+
     // History for all users and all packages
-    private SparseArray<ArrayMap<String,PackageHistory>> mIdleHistory = new SparseArray<>();
+    private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>();
     private long mLastPeriod = 0;
     private static final long ONE_MINUTE = 60 * 1000;
     private static final int HISTORY_SIZE = 100;
@@ -70,6 +75,13 @@
     private static final String ATTR_SCREEN_IDLE = "screenIdleTime";
     // Elapsed timebase time when app was last used
     private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime";
+    private static final String ATTR_CURRENT_BUCKET = "appLimitBucket";
+    private static final String ATTR_BUCKETING_REASON = "bucketReason";
+
+    // State that was last informed to listeners, since boot
+    private static final int STATE_UNINFORMED = 0;
+    private static final int STATE_ACTIVE = 1;
+    private static final int STATE_IDLE = 2;
 
     // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
     private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
@@ -85,17 +97,15 @@
 
     private boolean mScreenOn;
 
-    private static class PackageHistory {
+    private static class AppUsageHistory {
         final byte[] recent = new byte[HISTORY_SIZE];
         long lastUsedElapsedTime;
         long lastUsedScreenTime;
+        @StandbyBuckets int currentBucket;
+        String bucketingReason;
+        int lastInformedState;
     }
 
-    AppIdleHistory(long elapsedRealtime) {
-        this(Environment.getDataSystemDirectory(), elapsedRealtime);
-    }
-
-    @VisibleForTesting
     AppIdleHistory(File storageDir, long elapsedRealtime) {
         mElapsedSnapshot = elapsedRealtime;
         mScreenOnSnapshot = elapsedRealtime;
@@ -119,6 +129,9 @@
             mElapsedDuration += elapsedRealtime - mElapsedSnapshot;
             mElapsedSnapshot = elapsedRealtime;
         }
+        if (DEBUG) Slog.d(TAG, "mScreenOnSnapshot=" + mScreenOnSnapshot
+                + ", mScreenOnDuration=" + mScreenOnDuration
+                + ", mScreenOn=" + mScreenOn);
     }
 
     public long getScreenOnTime(long elapsedRealtime) {
@@ -174,29 +187,35 @@
     }
 
     public void reportUsage(String packageName, int userId, long elapsedRealtime) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
-        PackageHistory packageHistory = getPackageHistory(userHistory, packageName,
-                elapsedRealtime);
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, true);
 
         shiftHistoryToNow(userHistory, elapsedRealtime);
 
-        packageHistory.lastUsedElapsedTime = mElapsedDuration
+        appUsageHistory.lastUsedElapsedTime = mElapsedDuration
                 + (elapsedRealtime - mElapsedSnapshot);
-        packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
-        packageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
+        appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
+        appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
+        appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_ACTIVE;
+        appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
+        if (DEBUG) {
+            Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+                    + ", reason=" + appUsageHistory.bucketingReason);
+        }
     }
 
     public void setIdle(String packageName, int userId, long elapsedRealtime) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
-        PackageHistory packageHistory = getPackageHistory(userHistory, packageName,
-                elapsedRealtime);
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, true);
 
         shiftHistoryToNow(userHistory, elapsedRealtime);
 
-        packageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
+        appUsageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
     }
 
-    private void shiftHistoryToNow(ArrayMap<String, PackageHistory> userHistory,
+    private void shiftHistoryToNow(ArrayMap<String, AppUsageHistory> userHistory,
             long elapsedRealtime) {
         long thisPeriod = elapsedRealtime / PERIOD_DURATION;
         // Has the period switched over? Slide all users' package histories
@@ -206,7 +225,7 @@
             final int NUSERS = mIdleHistory.size();
             for (int u = 0; u < NUSERS; u++) {
                 userHistory = mIdleHistory.valueAt(u);
-                for (PackageHistory idleState : userHistory.values()) {
+                for (AppUsageHistory idleState : userHistory.values()) {
                     // Shift left
                     System.arraycopy(idleState.recent, diff, idleState.recent, 0,
                             HISTORY_SIZE - diff);
@@ -221,8 +240,8 @@
         mLastPeriod = thisPeriod;
     }
 
-    private ArrayMap<String, PackageHistory> getUserHistory(int userId) {
-        ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
+    private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) {
+        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
         if (userHistory == null) {
             userHistory = new ArrayMap<>();
             mIdleHistory.put(userId, userHistory);
@@ -231,16 +250,18 @@
         return userHistory;
     }
 
-    private PackageHistory getPackageHistory(ArrayMap<String, PackageHistory> userHistory,
-            String packageName, long elapsedRealtime) {
-        PackageHistory packageHistory = userHistory.get(packageName);
-        if (packageHistory == null) {
-            packageHistory = new PackageHistory();
-            packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime);
-            packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
-            userHistory.put(packageName, packageHistory);
+    private AppUsageHistory getPackageHistory(ArrayMap<String, AppUsageHistory> userHistory,
+            String packageName, long elapsedRealtime, boolean create) {
+        AppUsageHistory appUsageHistory = userHistory.get(packageName);
+        if (appUsageHistory == null && create) {
+            appUsageHistory = new AppUsageHistory();
+            appUsageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime);
+            appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
+            appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_NEVER;
+            appUsageHistory.bucketingReason = REASON_DEFAULT;
+            userHistory.put(packageName, appUsageHistory);
         }
-        return packageHistory;
+        return appUsageHistory;
     }
 
     public void onUserRemoved(int userId) {
@@ -248,48 +269,124 @@
     }
 
     public boolean isIdle(String packageName, int userId, long elapsedRealtime) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
-        PackageHistory packageHistory =
-                getPackageHistory(userHistory, packageName, elapsedRealtime);
-        if (packageHistory == null) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+        if (appUsageHistory == null) {
             return false; // Default to not idle
         } else {
-            return hasPassedThresholds(packageHistory, elapsedRealtime);
+            return appUsageHistory.currentBucket >= AppStandby.STANDBY_BUCKET_RARE;
+            // Whether or not it's passed will now be externally calculated and the
+            // bucket will be pushed to the history using setAppStandbyBucket()
+            //return hasPassedThresholds(appUsageHistory, elapsedRealtime);
         }
     }
 
+    public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime,
+            int bucket, String reason) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+        appUsageHistory.currentBucket = bucket;
+        appUsageHistory.bucketingReason = reason;
+        if (DEBUG) {
+            Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+                    + ", reason=" + appUsageHistory.bucketingReason);
+        }
+    }
+
+    public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+        return appUsageHistory.currentBucket;
+    }
+
+    public String getAppStandbyReason(String packageName, int userId, long elapsedRealtime) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, false);
+        return appUsageHistory != null ? appUsageHistory.bucketingReason : null;
+    }
+
     private long getElapsedTime(long elapsedRealtime) {
         return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration);
     }
 
     public void setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
-        PackageHistory packageHistory = getPackageHistory(userHistory, packageName,
-                elapsedRealtime);
-        packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime)
-                - mElapsedTimeThreshold;
-        packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime)
-                - (idle ? mScreenOnTimeThreshold : 0) - 1000 /* just a second more */;
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, true);
+        if (idle) {
+            appUsageHistory.currentBucket = STANDBY_BUCKET_RARE;
+            appUsageHistory.bucketingReason = REASON_FORCED;
+        } else {
+            appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
+            // This is to pretend that the app was just used, don't freeze the state anymore.
+            appUsageHistory.bucketingReason = REASON_USAGE;
+        }
     }
 
     public void clearUsage(String packageName, int userId) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         userHistory.remove(packageName);
     }
 
-    private boolean hasPassedThresholds(PackageHistory packageHistory, long elapsedRealtime) {
-        return (packageHistory.lastUsedScreenTime
-                    <= getScreenOnTime(elapsedRealtime) - mScreenOnTimeThreshold)
-                && (packageHistory.lastUsedElapsedTime
-                        <= getElapsedTime(elapsedRealtime) - mElapsedTimeThreshold);
+    boolean shouldInformListeners(String packageName, int userId,
+            long elapsedRealtime, boolean isIdle) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, true);
+        int targetState = isIdle? STATE_IDLE : STATE_ACTIVE;
+        if (appUsageHistory.lastInformedState != (isIdle ? STATE_IDLE : STATE_ACTIVE)) {
+            appUsageHistory.lastInformedState = targetState;
+            return true;
+        }
+        return false;
     }
 
-    private File getUserFile(int userId) {
+    /**
+     * Returns the index in the arrays of screenTimeThresholds and elapsedTimeThresholds
+     * that corresponds to how long since the app was used.
+     * @param packageName
+     * @param userId
+     * @param elapsedRealtime current time
+     * @param screenTimeThresholds Array of screen times, in ascending order, first one is 0
+     * @param elapsedTimeThresholds Array of elapsed time, in ascending order, first one is 0
+     * @return The index whose values the app's used time exceeds (in both arrays)
+     */
+    int getThresholdIndex(String packageName, int userId, long elapsedRealtime,
+            long[] screenTimeThresholds, long[] elapsedTimeThresholds) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, false);
+        // If we don't have any state for the app, assume never used
+        if (appUsageHistory == null) return screenTimeThresholds.length - 1;
+
+        long screenOnDelta = getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime;
+        long elapsedDelta = getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime;
+
+        if (DEBUG) Slog.d(TAG, packageName
+                + " lastUsedScreen=" + appUsageHistory.lastUsedScreenTime
+                + " lastUsedElapsed=" + appUsageHistory.lastUsedElapsedTime);
+        if (DEBUG) Slog.d(TAG, packageName + " screenOn=" + screenOnDelta
+                + ", elapsed=" + elapsedDelta);
+        for (int i = screenTimeThresholds.length - 1; i >= 0; i--) {
+            if (screenOnDelta >= screenTimeThresholds[i]
+                && elapsedDelta >= elapsedTimeThresholds[i]) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    @VisibleForTesting
+    File getUserFile(int userId) {
         return new File(new File(new File(mStorageDir, "users"),
                 Integer.toString(userId)), APP_IDLE_FILENAME);
     }
 
-    private void readAppIdleTimes(int userId, ArrayMap<String, PackageHistory> userHistory) {
+    private void readAppIdleTimes(int userId, ArrayMap<String, AppUsageHistory> userHistory) {
         FileInputStream fis = null;
         try {
             AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
@@ -315,12 +412,22 @@
                     final String name = parser.getName();
                     if (name.equals(TAG_PACKAGE)) {
                         final String packageName = parser.getAttributeValue(null, ATTR_NAME);
-                        PackageHistory packageHistory = new PackageHistory();
-                        packageHistory.lastUsedElapsedTime =
+                        AppUsageHistory appUsageHistory = new AppUsageHistory();
+                        appUsageHistory.lastUsedElapsedTime =
                                 Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE));
-                        packageHistory.lastUsedScreenTime =
+                        appUsageHistory.lastUsedScreenTime =
                                 Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE));
-                        userHistory.put(packageName, packageHistory);
+                        String currentBucketString = parser.getAttributeValue(null,
+                                ATTR_CURRENT_BUCKET);
+                        appUsageHistory.currentBucket = currentBucketString == null
+                                ? AppStandby.STANDBY_BUCKET_ACTIVE
+                                : Integer.parseInt(currentBucketString);
+                        appUsageHistory.bucketingReason =
+                                parser.getAttributeValue(null, ATTR_BUCKETING_REASON);
+                        if (appUsageHistory.bucketingReason == null) {
+                            appUsageHistory.bucketingReason = REASON_DEFAULT;
+                        }
+                        userHistory.put(packageName, appUsageHistory);
                     }
                 }
             }
@@ -345,17 +452,20 @@
 
             xml.startTag(null, TAG_PACKAGES);
 
-            ArrayMap<String,PackageHistory> userHistory = getUserHistory(userId);
+            ArrayMap<String,AppUsageHistory> userHistory = getUserHistory(userId);
             final int N = userHistory.size();
             for (int i = 0; i < N; i++) {
                 String packageName = userHistory.keyAt(i);
-                PackageHistory history = userHistory.valueAt(i);
+                AppUsageHistory history = userHistory.valueAt(i);
                 xml.startTag(null, TAG_PACKAGE);
                 xml.attribute(null, ATTR_NAME, packageName);
                 xml.attribute(null, ATTR_ELAPSED_IDLE,
                         Long.toString(history.lastUsedElapsedTime));
                 xml.attribute(null, ATTR_SCREEN_IDLE,
                         Long.toString(history.lastUsedScreenTime));
+                xml.attribute(null, ATTR_CURRENT_BUCKET,
+                        Integer.toString(history.currentBucket));
+                xml.attribute(null, ATTR_BUCKETING_REASON, history.bucketingReason);
                 xml.endTag(null, TAG_PACKAGE);
             }
 
@@ -368,10 +478,10 @@
         }
     }
 
-    public void dump(IndentingPrintWriter idpw, int userId) {
+    public void dump(IndentingPrintWriter idpw, int userId, String pkg) {
         idpw.println("Package idle stats:");
         idpw.increaseIndent();
-        ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
+        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long totalElapsedTime = getElapsedTime(elapsedRealtime);
         final long screenOnTime = getScreenOnTime(elapsedRealtime);
@@ -379,13 +489,18 @@
         final int P = userHistory.size();
         for (int p = 0; p < P; p++) {
             final String packageName = userHistory.keyAt(p);
-            final PackageHistory packageHistory = userHistory.valueAt(p);
+            final AppUsageHistory appUsageHistory = userHistory.valueAt(p);
+            if (pkg != null && !pkg.equals(packageName)) {
+                continue;
+            }
             idpw.print("package=" + packageName);
             idpw.print(" lastUsedElapsed=");
-            TimeUtils.formatDuration(totalElapsedTime - packageHistory.lastUsedElapsedTime, idpw);
+            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw);
             idpw.print(" lastUsedScreenOn=");
-            TimeUtils.formatDuration(screenOnTime - packageHistory.lastUsedScreenTime, idpw);
+            TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw);
             idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
+            idpw.print(" bucket=" + appUsageHistory.currentBucket
+                    + " reason=" + appUsageHistory.bucketingReason);
             idpw.println();
         }
         idpw.println();
@@ -399,7 +514,7 @@
     }
 
     public void dumpHistory(IndentingPrintWriter idpw, int userId) {
-        ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
+        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         if (userHistory == null) return;
         final int P = userHistory.size();
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index b2446ba..17fde57 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -18,13 +18,14 @@
 
 import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
-import static com.android.server.usage.UsageStatsService.MSG_REPORT_EVENT;
 
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.admin.DevicePolicyManager;
+import android.app.usage.AppStandby;
+import android.app.usage.AppStandby.StandbyBuckets;
 import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManagerInternal;
+import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
 import android.appwidget.AppWidgetManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -41,6 +42,7 @@
 import android.net.NetworkScoreManager;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.IDeviceIdleController;
 import android.os.Looper;
@@ -66,8 +68,10 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 
+import java.io.File;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -76,10 +80,33 @@
 public class AppStandbyController {
 
     private static final String TAG = "AppStandbyController";
-    private static final boolean DEBUG = false;
+    static final boolean DEBUG = false;
 
     static final boolean COMPRESS_TIME = false;
     private static final long ONE_MINUTE = 60 * 1000;
+    private static final long ONE_HOUR = ONE_MINUTE * 60;
+    private static final long ONE_DAY = ONE_HOUR * 24;
+
+    static final long[] SCREEN_TIME_THRESHOLDS = {
+            0,
+            0,
+            COMPRESS_TIME ? 120 * 1000 : 1 * ONE_HOUR,
+            COMPRESS_TIME ? 240 * 1000 : 8 * ONE_HOUR
+    };
+
+    static final long[] ELAPSED_TIME_THRESHOLDS = {
+            0,
+            COMPRESS_TIME ?  1 * ONE_MINUTE : 12 * ONE_HOUR,
+            COMPRESS_TIME ?  4 * ONE_MINUTE :  2 * ONE_DAY,
+            COMPRESS_TIME ? 16 * ONE_MINUTE :  8 * ONE_DAY
+    };
+
+    static final int[] THRESHOLD_BUCKETS = {
+            AppStandby.STANDBY_BUCKET_ACTIVE,
+            AppStandby.STANDBY_BUCKET_WORKING_SET,
+            AppStandby.STANDBY_BUCKET_FREQUENT,
+            AppStandby.STANDBY_BUCKET_RARE
+    };
 
     // To name the lock for stack traces
     static class Lock {}
@@ -92,7 +119,7 @@
     private AppIdleHistory mAppIdleHistory;
 
     @GuardedBy("mAppIdleLock")
-    private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
+    private ArrayList<AppIdleStateChangeListener>
             mPackageAccessListeners = new ArrayList<>();
 
     /** Whether we've queried the list of carrier privileged apps. */
@@ -118,6 +145,9 @@
     long mAppIdleWallclockThresholdMillis;
     long mAppIdleParoleIntervalMillis;
     long mAppIdleParoleDurationMillis;
+    long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
+    long[] mAppStandbyElapsedThresholds = ELAPSED_TIME_THRESHOLDS;
+
     boolean mAppIdleEnabled;
     boolean mAppIdleTempParoled;
     boolean mCharging;
@@ -129,20 +159,26 @@
     private final Handler mHandler;
     private final Context mContext;
 
-    private DisplayManager mDisplayManager;
-    private IDeviceIdleController mDeviceIdleController;
+    // TODO: Provide a mechanism to set an external bucketing service
+    private boolean mUseInternalBucketingHeuristics = true;
+
     private AppWidgetManager mAppWidgetManager;
-    private IBatteryStats mBatteryStats;
     private PowerManager mPowerManager;
     private PackageManager mPackageManager;
-    private PackageManagerInternal mPackageManagerInternal;
+    private Injector mInjector;
+
 
     AppStandbyController(Context context, Looper looper) {
-        mContext = context;
-        mHandler = new AppStandbyHandler(looper);
+        this(new Injector(context, looper));
+    }
+
+    AppStandbyController(Injector injector) {
+        mInjector = injector;
+        mContext = mInjector.getContext();
+        mHandler = new AppStandbyHandler(mInjector.getLooper());
         mPackageManager = mContext.getPackageManager();
-        mAppIdleEnabled = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_enableAutoPowerModes);
+        mAppIdleEnabled = mInjector.isAppIdleEnabled();
+
         if (mAppIdleEnabled) {
             IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
             deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
@@ -150,7 +186,8 @@
             mContext.registerReceiver(new DeviceStateReceiver(), deviceStates);
         }
         synchronized (mAppIdleLock) {
-            mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime());
+            mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(),
+                    mInjector.elapsedRealtime());
         }
 
         IntentFilter packageFilter = new IntentFilter();
@@ -164,6 +201,7 @@
     }
 
     public void onBootPhase(int phase) {
+        mInjector.onBootPhase(phase);
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
             // Observe changes to the threshold
             SettingsObserver settingsObserver = new SettingsObserver(mHandler);
@@ -171,18 +209,11 @@
             settingsObserver.updateSettings();
 
             mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class);
-            mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
-                    ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-            mBatteryStats = IBatteryStats.Stub.asInterface(
-                    ServiceManager.getService(BatteryStats.SERVICE_NAME));
-            mDisplayManager = (DisplayManager) mContext.getSystemService(
-                    Context.DISPLAY_SERVICE);
             mPowerManager = mContext.getSystemService(PowerManager.class);
-            mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
 
-            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+            mInjector.registerDisplayListener(mDisplayListener, mHandler);
             synchronized (mAppIdleLock) {
-                mAppIdleHistory.updateDisplay(isDisplayOn(), SystemClock.elapsedRealtime());
+                mAppIdleHistory.updateDisplay(isDisplayOn(), mInjector.elapsedRealtime());
             }
 
             if (mPendingOneTimeCheckIdleStates) {
@@ -191,7 +222,7 @@
 
             mSystemServicesReady = true;
         } else if (phase == PHASE_BOOT_COMPLETED) {
-            setChargingState(mContext.getSystemService(BatteryManager.class).isCharging());
+            setChargingState(mInjector.isCharging());
         }
     }
 
@@ -229,7 +260,7 @@
     /** Paroled here means temporary pardon from being inactive */
     void setAppIdleParoled(boolean paroled) {
         synchronized (mAppIdleLock) {
-            final long now = System.currentTimeMillis();
+            final long now = mInjector.currentTimeMillis();
             if (mAppIdleTempParoled != paroled) {
                 mAppIdleTempParoled = paroled;
                 if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
@@ -284,7 +315,7 @@
      * scheduling a series of repeating checkIdleStates each time we fired off one.
      */
     void postOneTimeCheckIdleStates() {
-        if (mDeviceIdleController == null) {
+        if (mInjector.getBootPhase() < PHASE_SYSTEM_SERVICES_READY) {
             // Not booted yet; wait for it!
             mPendingOneTimeCheckIdleStates = true;
         } else {
@@ -304,7 +335,7 @@
 
         final int[] runningUserIds;
         try {
-            runningUserIds = ActivityManager.getService().getRunningUserIds();
+            runningUserIds = mInjector.getRunningUserIds();
             if (checkUserId != UserHandle.USER_ALL
                     && !ArrayUtils.contains(runningUserIds, checkUserId)) {
                 return false;
@@ -313,7 +344,7 @@
             throw re.rethrowFromSystemServer();
         }
 
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long elapsedRealtime = mInjector.elapsedRealtime();
         for (int i = 0; i < runningUserIds.length; i++) {
             final int userId = runningUserIds[i];
             if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
@@ -329,30 +360,71 @@
             for (int p = 0; p < packageCount; p++) {
                 final PackageInfo pi = packages.get(p);
                 final String packageName = pi.packageName;
-                final boolean isIdle = isAppIdleFiltered(packageName,
+                final boolean isSpecial = isAppSpecial(packageName,
                         UserHandle.getAppId(pi.applicationInfo.uid),
-                        userId, elapsedRealtime);
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
-                        userId, isIdle ? 1 : 0, packageName));
-                if (isIdle) {
+                        userId);
+                if (DEBUG) {
+                    Slog.d(TAG, "   Checking idle state for " + packageName);
+                }
+                if (isSpecial) {
+                    maybeInformListeners(packageName, userId, elapsedRealtime, false);
+                } else if (mUseInternalBucketingHeuristics) {
                     synchronized (mAppIdleLock) {
-                        mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
+                        int oldBucket = mAppIdleHistory.getAppStandbyBucket(packageName, userId,
+                                elapsedRealtime);
+                        String bucketingReason = mAppIdleHistory.getAppStandbyReason(packageName,
+                                userId, elapsedRealtime);
+                        if (bucketingReason != null
+                                && (bucketingReason.equals(AppStandby.REASON_FORCED)
+                                    || bucketingReason.startsWith(AppStandby.REASON_PREDICTED))) {
+                            continue;
+                        }
+                        int newBucket = getBucketForLocked(packageName, userId,
+                                            elapsedRealtime);
+                        if (DEBUG) {
+                            Slog.d(TAG, "     Old bucket=" + oldBucket
+                                    + ", newBucket=" + newBucket);
+                        }
+                        if (oldBucket != newBucket) {
+                            mAppIdleHistory.setAppStandbyBucket(packageName, userId,
+                                    elapsedRealtime, newBucket, AppStandby.REASON_TIMEOUT);
+                            maybeInformListeners(packageName, userId, elapsedRealtime,
+                                    newBucket >= AppStandby.STANDBY_BUCKET_RARE);
+                        }
                     }
                 }
             }
         }
         if (DEBUG) {
             Slog.d(TAG, "checkIdleStates took "
-                    + (SystemClock.elapsedRealtime() - elapsedRealtime));
+                    + (mInjector.elapsedRealtime() - elapsedRealtime));
         }
         return true;
     }
 
+    private void maybeInformListeners(String packageName, int userId,
+            long elapsedRealtime, boolean isIdle) {
+        synchronized (mAppIdleLock) {
+            if (mAppIdleHistory.shouldInformListeners(packageName, userId,
+                    elapsedRealtime, isIdle)) {
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
+                        userId, isIdle ? 1 : 0, packageName));
+            }
+        }
+    }
+
+    @StandbyBuckets int getBucketForLocked(String packageName, int userId,
+            long elapsedRealtime) {
+        int bucketIndex = mAppIdleHistory.getThresholdIndex(packageName, userId,
+                elapsedRealtime, mAppStandbyScreenThresholds, mAppStandbyElapsedThresholds);
+        return THRESHOLD_BUCKETS[bucketIndex];
+    }
+
     /** Check if it's been a while since last parole and let idle apps do some work */
     void checkParoleTimeout() {
         boolean setParoled = false;
         synchronized (mAppIdleLock) {
-            final long now = System.currentTimeMillis();
+            final long now = mInjector.currentTimeMillis();
             if (!mAppIdleTempParoled) {
                 final long timeSinceLastParole = now - mLastAppIdleParoledTime;
                 if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
@@ -374,10 +446,10 @@
             final int uid = mPackageManager.getPackageUidAsUser(packageName,
                     PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
             if (idle) {
-                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
+                mInjector.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
                         packageName, uid);
             } else {
-                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
+                mInjector.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
                         packageName, uid);
             }
         } catch (PackageManager.NameNotFoundException | RemoteException e) {
@@ -389,7 +461,7 @@
         if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
         boolean paroled = false;
         synchronized (mAppIdleLock) {
-            final long timeSinceLastParole = System.currentTimeMillis() - mLastAppIdleParoledTime;
+            final long timeSinceLastParole = mInjector.currentTimeMillis() - mLastAppIdleParoledTime;
             if (!deviceIdle
                     && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
                 if (DEBUG) {
@@ -419,13 +491,11 @@
                     || event.mEventType == UsageEvents.Event.USER_INTERACTION)) {
                 mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
                 if (previouslyIdle) {
-                    mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
-                                /* idle = */ 0, event.mPackage));
+                    maybeInformListeners(event.mPackage, userId, elapsedRealtime, false);
                     notifyBatteryStats(event.mPackage, userId, false);
                 }
             }
         }
-
     }
 
     /**
@@ -439,7 +509,7 @@
     void forceIdleState(String packageName, int userId, boolean idle) {
         final int appId = getAppId(packageName);
         if (appId < 0) return;
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long elapsedRealtime = mInjector.elapsedRealtime();
 
         final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
                 userId, elapsedRealtime);
@@ -470,7 +540,7 @@
         }
     }
 
-    void addListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) {
+    void addListener(AppIdleStateChangeListener listener) {
         synchronized (mAppIdleLock) {
             if (!mPackageAccessListeners.contains(listener)) {
                 mPackageAccessListeners.add(listener);
@@ -478,7 +548,7 @@
         }
     }
 
-    void removeListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) {
+    void removeListener(AppIdleStateChangeListener listener) {
         synchronized (mAppIdleLock) {
             mPackageAccessListeners.remove(listener);
         }
@@ -501,12 +571,66 @@
             return false;
         }
         if (shouldObfuscateInstantApps &&
-                mPackageManagerInternal.isPackageEphemeral(userId, packageName)) {
+                mInjector.isPackageEphemeral(userId, packageName)) {
             return false;
         }
         return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
     }
 
+    /** Returns true if this app should be whitelisted for some reason, to never go into standby */
+    boolean isAppSpecial(String packageName, int appId, int userId) {
+        if (packageName == null) return false;
+        // If not enabled at all, of course nobody is ever idle.
+        if (!mAppIdleEnabled) {
+            return true;
+        }
+        if (appId < Process.FIRST_APPLICATION_UID) {
+            // System uids never go idle.
+            return true;
+        }
+        if (packageName.equals("android")) {
+            // Nor does the framework (which should be redundant with the above, but for MR1 we will
+            // retain this for safety).
+            return true;
+        }
+        if (mSystemServicesReady) {
+            try {
+                // We allow all whitelisted apps, including those that don't want to be whitelisted
+                // for idle mode, because app idle (aka app standby) is really not as big an issue
+                // for controlling who participates vs. doze mode.
+                if (mInjector.isPowerSaveWhitelistExceptIdleApp(packageName)) {
+                    return true;
+                }
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+
+            if (isActiveDeviceAdmin(packageName, userId)) {
+                return true;
+            }
+
+            if (isActiveNetworkScorer(packageName)) {
+                return true;
+            }
+
+            if (mAppWidgetManager != null
+                    && mInjector.isBoundWidgetPackage(mAppWidgetManager, packageName, userId)) {
+                return true;
+            }
+
+            if (isDeviceProvisioningPackage(packageName)) {
+                return true;
+            }
+        }
+
+        // Check this last, as it can be the most expensive check
+        if (isCarrierApp(packageName)) {
+            return true;
+        }
+
+        return false;
+    }
+
     /**
      * Checks if an app has been idle for a while and filters out apps that are excluded.
      * It returns false if the current system state allows all apps to be considered active.
@@ -515,61 +639,11 @@
      */
     boolean isAppIdleFiltered(String packageName, int appId, int userId,
             long elapsedRealtime) {
-        if (packageName == null) return false;
-        // If not enabled at all, of course nobody is ever idle.
-        if (!mAppIdleEnabled) {
+        if (isAppSpecial(packageName, appId, userId)) {
             return false;
+        } else {
+            return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
         }
-        if (appId < Process.FIRST_APPLICATION_UID) {
-            // System uids never go idle.
-            return false;
-        }
-        if (packageName.equals("android")) {
-            // Nor does the framework (which should be redundant with the above, but for MR1 we will
-            // retain this for safety).
-            return false;
-        }
-        if (mSystemServicesReady) {
-            try {
-                // We allow all whitelisted apps, including those that don't want to be whitelisted
-                // for idle mode, because app idle (aka app standby) is really not as big an issue
-                // for controlling who participates vs. doze mode.
-                if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
-                    return false;
-                }
-            } catch (RemoteException re) {
-                throw re.rethrowFromSystemServer();
-            }
-
-            if (isActiveDeviceAdmin(packageName, userId)) {
-                return false;
-            }
-
-            if (isActiveNetworkScorer(packageName)) {
-                return false;
-            }
-
-            if (mAppWidgetManager != null
-                    && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) {
-                return false;
-            }
-
-            if (isDeviceProvisioningPackage(packageName)) {
-                return false;
-            }
-        }
-
-        if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
-            return false;
-        }
-
-        // Check this last, as it is the most expensive check
-        // TODO: Optimize this by fetching the carrier privileged apps ahead of time
-        if (isCarrierApp(packageName)) {
-            return false;
-        }
-
-        return true;
     }
 
     int[] getIdleUidsForUser(int userId) {
@@ -577,7 +651,7 @@
             return new int[0];
         }
 
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long elapsedRealtime = mInjector.elapsedRealtime();
 
         List<ApplicationInfo> apps;
         try {
@@ -613,7 +687,7 @@
             }
         }
         if (DEBUG) {
-            Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime));
+            Slog.d(TAG, "getIdleUids took " + (mInjector.elapsedRealtime() - elapsedRealtime));
         }
         int numIdle = 0;
         for (int i = uidStates.size() - 1; i >= 0; i--) {
@@ -643,6 +717,21 @@
                 .sendToTarget();
     }
 
+    @StandbyBuckets int getAppStandbyBucket(String packageName, int userId,
+            long elapsedRealtime, boolean shouldObfuscateInstantApps) {
+        if (shouldObfuscateInstantApps &&
+                mInjector.isPackageEphemeral(userId, packageName)) {
+            return AppStandby.STANDBY_BUCKET_ACTIVE;
+        }
+
+        return mAppIdleHistory.getAppStandbyBucket(packageName, userId, elapsedRealtime);
+    }
+
+    void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
+            String reason, long elapsedRealtime) {
+        mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason);
+    }
+
     private boolean isActiveDeviceAdmin(String packageName, int userId) {
         DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
         if (dpm == null) return false;
@@ -662,7 +751,7 @@
     private boolean isCarrierApp(String packageName) {
         synchronized (mAppIdleLock) {
             if (!mHaveCarrierPrivilegedApps) {
-                fetchCarrierPrivilegedAppsLA();
+                fetchCarrierPrivilegedAppsLocked();
             }
             if (mCarrierPrivilegedApps != null) {
                 return mCarrierPrivilegedApps.contains(packageName);
@@ -682,7 +771,7 @@
     }
 
     @GuardedBy("mAppIdleLock")
-    private void fetchCarrierPrivilegedAppsLA() {
+    private void fetchCarrierPrivilegedAppsLocked() {
         TelephonyManager telephonyManager =
                 mContext.getSystemService(TelephonyManager.class);
         mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges();
@@ -693,20 +782,19 @@
     }
 
     private boolean isActiveNetworkScorer(String packageName) {
-        NetworkScoreManager nsm = (NetworkScoreManager) mContext.getSystemService(
-                Context.NETWORK_SCORE_SERVICE);
-        return packageName != null && packageName.equals(nsm.getActiveScorerPackage());
+        String activeScorer = mInjector.getActiveNetworkScorer();
+        return packageName != null && packageName.equals(activeScorer);
     }
 
     void informListeners(String packageName, int userId, boolean isIdle) {
-        for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) {
+        for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
             listener.onAppIdleStateChanged(packageName, userId, isIdle);
         }
     }
 
     void informParoleStateChanged() {
         final boolean paroled = isParoledOrCharging();
-        for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) {
+        for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
             listener.onParoleStateChanged(paroled);
         }
     }
@@ -726,8 +814,7 @@
     }
 
     boolean isDisplayOn() {
-        return mDisplayManager
-                .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
+        return mInjector.isDefaultDisplayOn();
     }
 
     void clearAppIdleForPackage(String packageName, int userId) {
@@ -755,7 +842,7 @@
 
     void initializeDefaultsForSystemApps(int userId) {
         Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long elapsedRealtime = mInjector.elapsedRealtime();
         List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
                 PackageManager.MATCH_DISABLED_COMPONENTS,
                 userId);
@@ -786,9 +873,9 @@
         }
     }
 
-    void dumpUser(IndentingPrintWriter idpw, int userId) {
+    void dumpUser(IndentingPrintWriter idpw, int userId, String pkg) {
         synchronized (mAppIdleLock) {
-            mAppIdleHistory.dump(idpw, userId);
+            mAppIdleHistory.dump(idpw, userId, pkg);
         }
     }
 
@@ -828,6 +915,116 @@
         pw.print(" mLastAppIdleParoledTime=");
         TimeUtils.formatDuration(mLastAppIdleParoledTime, pw);
         pw.println();
+        pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds));
+        pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds));
+    }
+
+    /**
+     * Injector for interaction with external code. Override methods to provide a mock
+     * implementation for tests.
+     * onBootPhase() must be called with at least the PHASE_SYSTEM_SERVICES_READY
+     */
+    static class Injector {
+
+        private final Context mContext;
+        private final Looper mLooper;
+        private IDeviceIdleController mDeviceIdleController;
+        private IBatteryStats mBatteryStats;
+        private PackageManagerInternal mPackageManagerInternal;
+        private DisplayManager mDisplayManager;
+        int mBootPhase;
+
+        Injector(Context context, Looper looper) {
+            mContext = context;
+            mLooper = looper;
+        }
+
+        Context getContext() {
+            return mContext;
+        }
+
+        Looper getLooper() {
+            return mLooper;
+        }
+
+        void onBootPhase(int phase) {
+            if (phase == PHASE_SYSTEM_SERVICES_READY) {
+                mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
+                        ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+                mBatteryStats = IBatteryStats.Stub.asInterface(
+                        ServiceManager.getService(BatteryStats.SERVICE_NAME));
+                mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+                mDisplayManager = (DisplayManager) mContext.getSystemService(
+                        Context.DISPLAY_SERVICE);
+            }
+            mBootPhase = phase;
+        }
+
+        int getBootPhase() {
+            return mBootPhase;
+        }
+
+        /**
+         * Returns the elapsed realtime since the device started. Override this
+         * to control the clock.
+         * @return elapsed realtime
+         */
+        long elapsedRealtime() {
+            return SystemClock.elapsedRealtime();
+        }
+
+        long currentTimeMillis() {
+            return System.currentTimeMillis();
+        }
+
+        boolean isAppIdleEnabled() {
+            return mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_enableAutoPowerModes);
+        }
+
+        boolean isCharging() {
+            return mContext.getSystemService(BatteryManager.class).isCharging();
+        }
+
+        boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
+            return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName);
+        }
+
+        File getDataSystemDirectory() {
+            return Environment.getDataSystemDirectory();
+        }
+
+        void noteEvent(int event, String packageName, int uid) throws RemoteException {
+            mBatteryStats.noteEvent(event, packageName, uid);
+        }
+
+        boolean isPackageEphemeral(int userId, String packageName) {
+            return mPackageManagerInternal.isPackageEphemeral(userId, packageName);
+        }
+
+        int[] getRunningUserIds() throws RemoteException {
+            return ActivityManager.getService().getRunningUserIds();
+        }
+
+        boolean isDefaultDisplayOn() {
+            return mDisplayManager
+                    .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
+        }
+
+        void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler) {
+            mDisplayManager.registerDisplayListener(listener, handler);
+        }
+
+        String getActiveNetworkScorer() {
+            NetworkScoreManager nsm = (NetworkScoreManager) mContext.getSystemService(
+                    Context.NETWORK_SCORE_SERVICE);
+            return nsm.getActiveScorerPackage();
+        }
+
+        public boolean isBoundWidgetPackage(AppWidgetManager appWidgetManager, String packageName,
+                int userId) {
+            return appWidgetManager.isBoundWidgetPackage(packageName, userId);
+        }
     }
 
     class AppStandbyHandler extends Handler {
@@ -839,6 +1036,10 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
+                case MSG_INFORM_LISTENERS:
+                    informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1);
+                    break;
+
                 case MSG_FORCE_IDLE_STATE:
                     forceIdleState((String) msg.obj, msg.arg1, msg.arg2 == 1);
                     break;
@@ -911,7 +1112,7 @@
             if (displayId == Display.DEFAULT_DISPLAY) {
                 final boolean displayOn = isDisplayOn();
                 synchronized (mAppIdleLock) {
-                    mAppIdleHistory.updateDisplay(displayOn, SystemClock.elapsedRealtime());
+                    mAppIdleHistory.updateDisplay(displayOn, mInjector.elapsedRealtime());
                 }
             }
         }
@@ -931,6 +1132,8 @@
         private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold";
         private static final String KEY_PAROLE_INTERVAL = "parole_interval";
         private static final String KEY_PAROLE_DURATION = "parole_duration";
+        private static final String KEY_SCREEN_TIME_THRESHOLDS = "screen_thresholds";
+        private static final String KEY_ELAPSED_TIME_THRESHOLDS = "elapsed_thresholds";
 
         private final KeyValueListParser mParser = new KeyValueListParser(',');
 
@@ -969,7 +1172,7 @@
                         COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
 
                 mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
-                        COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours
+                        COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours
 
                 // Default: 24 hours between paroles
                 mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
@@ -979,9 +1182,35 @@
                         COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
                 mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
                         mAppIdleScreenThresholdMillis);
+
+                String screenThresholdsValue = mParser.getString(KEY_SCREEN_TIME_THRESHOLDS, null);
+                mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue,
+                        SCREEN_TIME_THRESHOLDS);
+
+                String elapsedThresholdsValue = mParser.getString(KEY_ELAPSED_TIME_THRESHOLDS, null);
+                mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue,
+                        ELAPSED_TIME_THRESHOLDS);
+            }
+        }
+
+        long[] parseLongArray(String values, long[] defaults) {
+            if (values == null) return defaults;
+            if (values.isEmpty()) {
+                // Reset to defaults
+                return defaults;
+            } else {
+                String[] thresholds = values.split("/");
+                if (thresholds.length == THRESHOLD_BUCKETS.length) {
+                    long[] array = new long[THRESHOLD_BUCKETS.length];
+                    for (int i = 0; i < THRESHOLD_BUCKETS.length; i++) {
+                        array[i] = Long.parseLong(thresholds[i]);
+                    }
+                    return array;
+                } else {
+                    return defaults;
+                }
             }
         }
     }
-
 }
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index afafea1..44e6a6c 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -20,6 +20,7 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
+import android.app.usage.AppStandby;
 import android.app.usage.ConfigurationStats;
 import android.app.usage.IUsageStatsManager;
 import android.app.usage.UsageEvents;
@@ -469,8 +470,32 @@
     void dump(String[] args, PrintWriter pw) {
         synchronized (mLock) {
             IndentingPrintWriter idpw = new IndentingPrintWriter(pw, "  ");
-            ArraySet<String> argSet = new ArraySet<>();
-            argSet.addAll(Arrays.asList(args));
+
+            boolean checkin = false;
+            boolean history = false;
+            String pkg = null;
+
+            if (args != null) {
+                for (int i = 0; i < args.length; i++) {
+                    String arg = args[i];
+                    if ("--checkin".equals(arg)) {
+                        checkin = true;
+                    } else if ("--history".equals(arg)) {
+                        history = true;
+                    } else if ("history".equals(arg)) {
+                        history = true;
+                        break;
+                    } else if ("flush".equals(arg)) {
+                        flushToDiskLocked();
+                        pw.println("Flushed stats to disk");
+                        return;
+                    } else {
+                        // Anything else is a pkg to filter
+                        pkg = arg;
+                        break;
+                    }
+                }
+            }
 
             final int userCount = mUserState.size();
             for (int i = 0; i < userCount; i++) {
@@ -478,26 +503,23 @@
                 idpw.printPair("user", userId);
                 idpw.println();
                 idpw.increaseIndent();
-                if (argSet.contains("--checkin")) {
+                if (checkin) {
                     mUserState.valueAt(i).checkin(idpw);
                 } else {
-                    mUserState.valueAt(i).dump(idpw);
+                    mUserState.valueAt(i).dump(idpw, pkg);
                     idpw.println();
-                    if (args.length > 0) {
-                        if ("history".equals(args[0])) {
-                            mAppStandby.dumpHistory(idpw, userId);
-                        } else if ("flush".equals(args[0])) {
-                            flushToDiskLocked();
-                            pw.println("Flushed stats to disk");
-                        }
+                    if (history) {
+                        mAppStandby.dumpHistory(idpw, userId);
                     }
                 }
-                mAppStandby.dumpUser(idpw, userId);
+                mAppStandby.dumpUser(idpw, userId, pkg);
                 idpw.decreaseIndent();
             }
 
-            pw.println();
-            mAppStandby.dumpState(args, pw);
+            if (pkg == null) {
+                pw.println();
+                mAppStandby.dumpState(args, pw);
+            }
         }
     }
 
@@ -654,6 +676,55 @@
         }
 
         @Override
+        public int getAppStandbyBucket(String packageName, String callingPackage, int userId) {
+            if (!hasPermission(callingPackage)) {
+                throw new SecurityException("Don't have permission to query app standby bucket");
+            }
+
+            final int callingUid = Binder.getCallingUid();
+            try {
+                userId = ActivityManager.getService().handleIncomingUser(
+                        Binder.getCallingPid(), callingUid, userId, false, true,
+                        "getAppStandbyBucket", null);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+            final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(callingUid,
+                    userId);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return mAppStandby.getAppStandbyBucket(packageName, userId,
+                        SystemClock.elapsedRealtime(), obfuscateInstantApps);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setAppStandbyBucket(String packageName,
+                int bucket, int userId) {
+            getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE,
+                    "No permission to change app standby state");
+
+            final int callingUid = Binder.getCallingUid();
+            try {
+                userId = ActivityManager.getService().handleIncomingUser(
+                        Binder.getCallingPid(), callingUid, userId, false, true,
+                        "setAppStandbyBucket", null);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mAppStandby.setAppStandbyBucket(packageName, userId, bucket,
+                        AppStandby.REASON_PREDICTED + ":" + callingUid,
+                        SystemClock.elapsedRealtime());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void whitelistAppTemporarily(String packageName, long duration, int userId)
                 throws RemoteException {
             StringBuilder reason = new StringBuilder(32);
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 0abbb82..0b10590 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -474,19 +474,19 @@
         mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
             @Override
             public boolean checkin(IntervalStats stats) {
-                printIntervalStats(pw, stats, false);
+                printIntervalStats(pw, stats, false, null);
                 return true;
             }
         });
     }
 
-    void dump(IndentingPrintWriter pw) {
+    void dump(IndentingPrintWriter pw, String pkg) {
         // This is not a check-in, only dump in-memory stats.
         for (int interval = 0; interval < mCurrentStats.length; interval++) {
             pw.print("In-memory ");
             pw.print(intervalToString(interval));
             pw.println(" stats");
-            printIntervalStats(pw, mCurrentStats[interval], true);
+            printIntervalStats(pw, mCurrentStats[interval], true, pkg);
         }
     }
 
@@ -505,7 +505,7 @@
     }
 
     void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats,
-            boolean prettyDates) {
+            boolean prettyDates, String pkg) {
         if (prettyDates) {
             pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
                     stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
@@ -521,6 +521,9 @@
         final int pkgCount = pkgStats.size();
         for (int i = 0; i < pkgCount; i++) {
             final UsageStats usageStats = pkgStats.valueAt(i);
+            if (pkg != null && !pkg.equals(usageStats.mPackageName)) {
+                continue;
+            }
             pw.printPair("package", usageStats.mPackageName);
             pw.printPair("totalTime",
                     formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
@@ -533,6 +536,9 @@
         pw.println("ChooserCounts");
         pw.increaseIndent();
         for (UsageStats usageStats : pkgStats.values()) {
+            if (pkg != null && !pkg.equals(usageStats.mPackageName)) {
+                continue;
+            }
             pw.printPair("package", usageStats.mPackageName);
             if (usageStats.mChooserCounts != null) {
                 final int chooserCountSize = usageStats.mChooserCounts.size();
@@ -555,19 +561,22 @@
         }
         pw.decreaseIndent();
 
-        pw.println("configurations");
-        pw.increaseIndent();
-        final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
-        final int configCount = configStats.size();
-        for (int i = 0; i < configCount; i++) {
-            final ConfigurationStats config = configStats.valueAt(i);
-            pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration));
-            pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
-            pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
-            pw.printPair("count", config.mActivationCount);
-            pw.println();
+        if (pkg == null) {
+            pw.println("configurations");
+            pw.increaseIndent();
+            final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
+            final int configCount = configStats.size();
+            for (int i = 0; i < configCount; i++) {
+                final ConfigurationStats config = configStats.valueAt(i);
+                pw.printPair("config", Configuration.resourceQualifierString(
+                        config.mConfiguration));
+                pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
+                pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
+                pw.printPair("count", config.mActivationCount);
+                pw.println();
+            }
+            pw.decreaseIndent();
         }
-        pw.decreaseIndent();
 
         pw.println("events");
         pw.increaseIndent();
@@ -575,6 +584,9 @@
         final int eventCount = events != null ? events.size() : 0;
         for (int i = 0; i < eventCount; i++) {
             final UsageEvents.Event event = events.valueAt(i);
+            if (pkg != null && !pkg.equals(event.mPackage)) {
+                continue;
+            }
             pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
             pw.printPair("type", eventToString(event.mEventType));
             pw.printPair("package", event.mPackage);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 99f8cfb..47b0f79 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1615,6 +1615,13 @@
     public static final String KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL =
             "skip_cf_fail_to_disable_dialog_bool";
 
+    /**
+     * List of the FAC (feature access codes) to dial as a normal call.
+     * @hide
+     */
+    public static final String KEY_FEATURE_ACCESS_CODES_STRING_ARRAY =
+            "feature_access_codes_string_array";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -1887,6 +1894,7 @@
         sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null);
         sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
         sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
+        sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
     }
 
     /**
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 98fb653..c3a2ceb 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -273,6 +273,13 @@
      * {@hide}
      */
     public static final int EMERGENCY_PERM_FAILURE = 64;
+
+    /**
+     * This cause is used to report a normal event only when no other cause in the normal class
+     * applies.
+     * {@hide}
+     */
+    public static final int NORMAL_UNSPECIFIED = 65;
     //*********************************************************************************************
     // When adding a disconnect type:
     // 1) Update toString() with the newly added disconnect type.
@@ -413,6 +420,8 @@
             return "EMERGENCY_TEMP_FAILURE";
         case EMERGENCY_PERM_FAILURE:
             return "EMERGENCY_PERM_FAILURE";
+        case NORMAL_UNSPECIFIED:
+            return "NORMAL_UNSPECIFIED";
         default:
             return "INVALID: " + cause;
         }
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index 060a518..29a95e6 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -106,9 +106,11 @@
     android.test.mock.stubs \
 
 LOCAL_SOURCE_FILES_ALL_GENERATED := true
+LOCAL_SDK_VERSION := current
 
 # Make sure to run droiddoc first to generate the stub source files.
 LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_runner_api_gen_stamp)
+android_test_runner_api_gen_stamp :=
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
@@ -203,6 +205,7 @@
 
 # Make sure to run droiddoc first to generate the stub source files.
 LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_mock_gen_stamp)
+android_test_mock_gen_stamp :=
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
@@ -246,6 +249,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := android.test.mock.sdk
+LOCAL_SDK_VERSION := current
 
 LOCAL_STATIC_JAVA_LIBRARIES := android.test.mock.stubs
 
diff --git a/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java b/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java
new file mode 100644
index 0000000..39f59f1
--- /dev/null
+++ b/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.reset;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.BroadcastInterceptingContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VersionedBroadcastListenerTest {
+    private static final String TAG = VersionedBroadcastListenerTest.class.getSimpleName();
+    private static final String ACTION_TEST = "action.test.happy.broadcasts";
+
+    @Mock private Context mContext;
+    private BroadcastInterceptingContext mServiceContext;
+    private Handler mHandler;
+    private VersionedBroadcastListener mListener;
+    private int mCallbackCount;
+
+    private void doCallback() { mCallbackCount++; }
+
+    private class MockContext extends BroadcastInterceptingContext {
+        MockContext(Context base) {
+            super(base);
+        }
+    }
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+    }
+
+    @Before public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        reset(mContext);
+        mServiceContext = new MockContext(mContext);
+        mHandler = new Handler(Looper.myLooper());
+        mCallbackCount = 0;
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_TEST);
+        mListener = new VersionedBroadcastListener(
+                TAG, mServiceContext, mHandler, filter, (Intent intent) -> doCallback());
+    }
+
+    @After public void tearDown() throws Exception {
+        if (mListener != null) {
+            mListener.stopListening();
+            mListener = null;
+        }
+    }
+
+    private void sendBroadcast() {
+        final Intent intent = new Intent(ACTION_TEST);
+        mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    @Test
+    public void testBasicListening() {
+        assertEquals(0, mCallbackCount);
+        mListener.startListening();
+        for (int i = 0; i < 5; i++) {
+            sendBroadcast();
+            assertEquals(i+1, mCallbackCount);
+        }
+        mListener.stopListening();
+    }
+
+    @Test
+    public void testBroadcastsBeforeStartAreIgnored() {
+        assertEquals(0, mCallbackCount);
+        for (int i = 0; i < 5; i++) {
+            sendBroadcast();
+            assertEquals(0, mCallbackCount);
+        }
+
+        mListener.startListening();
+        sendBroadcast();
+        assertEquals(1, mCallbackCount);
+    }
+
+    @Test
+    public void testBroadcastsAfterStopAreIgnored() {
+        mListener.startListening();
+        sendBroadcast();
+        assertEquals(1, mCallbackCount);
+        mListener.stopListening();
+
+        for (int i = 0; i < 5; i++) {
+            sendBroadcast();
+            assertEquals(1, mCallbackCount);
+        }
+    }
+}
diff --git a/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java b/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
index b5d333b..f58ea7e 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
@@ -48,8 +48,6 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class SimChangeListenerTest {
-    private static final int EVENT_UNM_UPDATE = 1;
-
     @Mock private Context mContext;
     private BroadcastInterceptingContext mServiceContext;
     private Handler mHandler;
diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp
new file mode 100644
index 0000000..6dd6059
--- /dev/null
+++ b/tools/locked_region_code_injection/Android.bp
@@ -0,0 +1,12 @@
+java_library_host {
+    name: "lockedregioncodeinjection",
+    manifest: "manifest.txt",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "asm-6.0",
+        "asm-commons-6.0",
+        "asm-tree-6.0",
+        "asm-analysis-6.0",
+        "guava-21.0",
+    ],
+}
diff --git a/tools/locked_region_code_injection/Android.mk b/tools/locked_region_code_injection/Android.mk
deleted file mode 100644
index 3f65151..0000000
--- a/tools/locked_region_code_injection/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_JAR_MANIFEST := manifest.txt
-LOCAL_MODULE := lockedregioncodeinjection
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    asm-6.0 \
-    asm-commons-6.0 \
-    asm-tree-6.0 \
-    asm-analysis-6.0 \
-    guava-21.0 \
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index b72df07..c2959d5 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -68,6 +68,7 @@
  * leaks within the calling process.
  * <p>
  * It deals with several categories of items:
+ * </p>
  * <ul>
  * <li>The list of configured networks. The list can be viewed and updated, and
  * attributes of individual entries can be modified.</li>
@@ -79,9 +80,11 @@
  * <li>It defines the names of various Intent actions that are broadcast upon
  * any sort of change in Wi-Fi state.
  * </ul>
+ * <p>
  * This is the API to use when performing Wi-Fi specific operations. To perform
  * operations that pertain to network connectivity at an abstract level, use
  * {@link android.net.ConnectivityManager}.
+ * </p>
  */
 @SystemService(Context.WIFI_SERVICE)
 public class WifiManager {
@@ -1575,7 +1578,21 @@
      * Request a scan for access points. Returns immediately. The availability
      * of the results is made known later by means of an asynchronous event sent
      * on completion of the scan.
-     * @return {@code true} if the operation succeeded, i.e., the scan was initiated
+     * <p>
+     * To initiate a Wi-Fi scan, declare the
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE}
+     * permission in the manifest, and perform these steps:
+     * </p>
+     * <ol style="1">
+     * <li>Invoke the following method:
+     * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).startScan()}</li>
+     * <li>
+     * Register a BroadcastReceiver to listen to
+     * {@code SCAN_RESULTS_AVAILABLE_ACTION}.</li>
+     * <li>When a broadcast is received, call:
+     * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).getScanResults()}</li>
+     * </ol>
+     * @return {@code true} if the operation succeeded, i.e., the scan was initiated.
      */
     public boolean startScan() {
         return startScan(null);