Merge "Fixing deadlock in OomAdjProfiler."
diff --git a/Android.bp b/Android.bp
index ee3b396..7219ef5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -136,7 +136,6 @@
         "core/java/android/content/pm/IDexModuleRegisterCallback.aidl",
         "core/java/android/content/pm/ILauncherApps.aidl",
         "core/java/android/content/pm/IOnAppsChangedListener.aidl",
-        "core/java/android/content/pm/IOnPermissionsChangeListener.aidl",
         "core/java/android/content/pm/IOtaDexopt.aidl",
         "core/java/android/content/pm/IPackageDataObserver.aidl",
         "core/java/android/content/pm/IPackageDeleteObserver.aidl",
@@ -278,6 +277,7 @@
         "core/java/android/os/storage/IStorageEventListener.aidl",
         "core/java/android/os/storage/IStorageShutdownObserver.aidl",
         "core/java/android/os/storage/IObbActionListener.aidl",
+        "core/java/android/permission/IOnPermissionsChangeListener.aidl",
         "core/java/android/permission/IPermissionController.aidl",
         "core/java/android/permission/IPermissionManager.aidl",
         ":keystore_aidl",
@@ -565,7 +565,7 @@
         "telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl",
         "telephony/java/android/telephony/ims/aidl/IImsServiceControllerListener.aidl",
         "telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl",
-        "telephony/java/android/telephony/ims/aidl/IRcs.aidl",
+        "telephony/java/android/telephony/ims/aidl/IRcsMessage.aidl",
         "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl",
         "telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl",
         "telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl",
diff --git a/config/hiddenapi-greylist-max-p.txt b/config/hiddenapi-greylist-max-p.txt
index 141e8e6..25d45b0 100644
--- a/config/hiddenapi-greylist-max-p.txt
+++ b/config/hiddenapi-greylist-max-p.txt
@@ -70,6 +70,8 @@
 Lcom/android/internal/R$styleable;->Searchable:[I
 Lcom/android/internal/R$styleable;->SearchableActionKey:[I
 Lcom/android/internal/telephony/IPhoneSubInfo$Stub;-><init>()V
+Lcom/android/internal/telephony/ITelephony;->getDataActivity()I
+Lcom/android/internal/telephony/ITelephony;->getDataState()I
 Lcom/android/internal/telephony/ITelephonyRegistry;->notifyCallForwardingChanged(Z)V
 Lcom/android/internal/telephony/ITelephonyRegistry;->notifyCellLocation(Landroid/os/Bundle;)V
 Lcom/android/internal/telephony/ITelephonyRegistry;->notifyDataActivity(I)V
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 1641afb..416aaa3 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -34,7 +34,6 @@
 import android.content.pm.ChangedPackages;
 import android.content.pm.ComponentInfo;
 import android.content.pm.FeatureInfo;
-import android.content.pm.IOnPermissionsChangeListener;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageManager;
@@ -82,6 +81,7 @@
 import android.os.UserManager;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
+import android.permission.IOnPermissionsChangeListener;
 import android.permission.IPermissionManager;
 import android.provider.Settings;
 import android.system.ErrnoException;
@@ -626,7 +626,7 @@
     @Override
     public int checkPermission(String permName, String pkgName) {
         try {
-            return mPM.checkPermission(permName, pkgName, getUserId());
+            return mPermissionManager.checkPermission(permName, pkgName, getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -707,46 +707,47 @@
     }
 
     @Override
-    public void revokeRuntimePermission(String packageName, String permissionName,
-            UserHandle user) {
+    public void revokeRuntimePermission(String packageName, String permName, UserHandle user) {
         if (DEBUG_TRACE_GRANTS
-                && shouldTraceGrant(packageName, permissionName, user.getIdentifier())) {
+                && shouldTraceGrant(packageName, permName, user.getIdentifier())) {
             Log.i(TAG, "App " + mContext.getPackageName() + " is revoking "
-                    + permissionName + " for user " + user.getIdentifier(), new RuntimeException());
+                    + permName + " for user " + user.getIdentifier(), new RuntimeException());
         }
         try {
-            mPM.revokeRuntimePermission(packageName, permissionName, user.getIdentifier());
+            mPermissionManager
+                    .revokeRuntimePermission(packageName, permName, user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     @Override
-    public int getPermissionFlags(String permissionName, String packageName, UserHandle user) {
+    public int getPermissionFlags(String permName, String packageName, UserHandle user) {
         try {
-            return mPM.getPermissionFlags(permissionName, packageName, user.getIdentifier());
+            return mPermissionManager
+                    .getPermissionFlags(permName, packageName, user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     @Override
-    public void updatePermissionFlags(String permissionName, String packageName,
+    public void updatePermissionFlags(String permName, String packageName,
             int flagMask, int flagValues, UserHandle user) {
         if (DEBUG_TRACE_GRANTS
-                && shouldTraceGrant(packageName, permissionName, user.getIdentifier())) {
+                && shouldTraceGrant(packageName, permName, user.getIdentifier())) {
             Log.i(TAG, "App " + mContext.getPackageName() + " is updating flags for "
-                    + permissionName + " for user " + user.getIdentifier() + ": "
+                    + permName + " for user " + user.getIdentifier() + ": "
                     + DebugUtils.flagsToString(PackageManager.class, "FLAG_PERMISSION_", flagMask)
                     + " := " + DebugUtils.flagsToString(
                             PackageManager.class, "FLAG_PERMISSION_", flagValues),
                     new RuntimeException());
         }
         try {
-            mPM.updatePermissionFlags(permissionName, packageName, flagMask,
-                    flagValues,
-                    mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.Q,
-                    user.getIdentifier());
+            final boolean checkAdjustPolicyFlagPermission =
+                    mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.Q;
+            mPermissionManager.updatePermissionFlags(permName, packageName, flagMask,
+                    flagValues, checkAdjustPolicyFlagPermission, user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -754,10 +755,11 @@
 
     @Override
     public @NonNull Set<String> getWhitelistedRestrictedPermissions(
-            @NonNull String packageName, @PermissionWhitelistFlags int whitelistFlags) {
+            @NonNull String packageName, @PermissionWhitelistFlags int flags) {
         try {
-            final List<String> whitelist = mPM.getWhitelistedRestrictedPermissions(
-                    packageName, whitelistFlags, getUserId());
+            final int userId = getUserId();
+            final List<String> whitelist = mPermissionManager
+                    .getWhitelistedRestrictedPermissions(packageName, flags, userId);
             if (whitelist != null) {
                 return new ArraySet<>(whitelist);
             }
@@ -769,10 +771,11 @@
 
     @Override
     public boolean addWhitelistedRestrictedPermission(@NonNull String packageName,
-            @NonNull String permission, @PermissionWhitelistFlags int whitelistFlags) {
+            @NonNull String permName, @PermissionWhitelistFlags int flags) {
         try {
-            return mPM.addWhitelistedRestrictedPermission(packageName, permission,
-                    whitelistFlags, getUserId());
+            final int userId = getUserId();
+            return mPermissionManager
+                    .addWhitelistedRestrictedPermission(packageName, permName, flags, userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -780,10 +783,11 @@
 
     @Override
     public boolean removeWhitelistedRestrictedPermission(@NonNull String packageName,
-            @NonNull String permission, @PermissionWhitelistFlags int whitelistFlags) {
+            @NonNull String permName, @PermissionWhitelistFlags int flags) {
         try {
-            return mPM.removeWhitelistedRestrictedPermission(packageName, permission,
-                    whitelistFlags, getUserId());
+            final int userId = getUserId();
+            return mPermissionManager
+                    .removeWhitelistedRestrictedPermission(packageName, permName, flags, userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1623,7 +1627,7 @@
             OnPermissionsChangeListenerDelegate delegate =
                     new OnPermissionsChangeListenerDelegate(listener, Looper.getMainLooper());
             try {
-                mPM.addOnPermissionsChangeListener(delegate);
+                mPermissionManager.addOnPermissionsChangeListener(delegate);
                 mPermissionListeners.put(listener, delegate);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -1637,7 +1641,7 @@
             IOnPermissionsChangeListener delegate = mPermissionListeners.get(listener);
             if (delegate != null) {
                 try {
-                    mPM.removeOnPermissionsChangeListener(delegate);
+                    mPermissionManager.removeOnPermissionsChangeListener(delegate);
                     mPermissionListeners.remove(listener);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d32b6b5..cfe2cf0 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -172,7 +172,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.euicc.EuiccCardManager;
 import android.telephony.euicc.EuiccManager;
-import android.telephony.ims.RcsManager;
+import android.telephony.ims.RcsMessageManager;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
@@ -614,11 +614,11 @@
                 return new SubscriptionManager(ctx.getOuterContext());
             }});
 
-        registerService(Context.TELEPHONY_RCS_SERVICE, RcsManager.class,
-                new CachedServiceFetcher<RcsManager>() {
+        registerService(Context.TELEPHONY_RCS_MESSAGE_SERVICE, RcsMessageManager.class,
+                new CachedServiceFetcher<RcsMessageManager>() {
                     @Override
-                    public RcsManager createService(ContextImpl ctx) {
-                        return new RcsManager(ctx.getOuterContext());
+                    public RcsMessageManager createService(ContextImpl ctx) {
+                        return new RcsMessageManager(ctx.getOuterContext());
                     }
                 });
 
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index dc07df8..4f8b60b 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -20,7 +20,6 @@
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.pm.IPackageManager;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
@@ -31,6 +30,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.permission.IPermissionManager;
 import android.util.Log;
 import android.view.IWindowManager;
 import android.view.InputEvent;
@@ -69,8 +69,8 @@
     private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager.Stub
             .asInterface(ServiceManager.getService(Service.ACCESSIBILITY_SERVICE));
 
-    private final IPackageManager mPackageManager = IPackageManager.Stub
-            .asInterface(ServiceManager.getService("package"));
+    private final IPermissionManager mPermissionManager = IPermissionManager.Stub
+            .asInterface(ServiceManager.getService("permissionmgr"));
 
     private final IActivityManager mActivityManager = IActivityManager.Stub
             .asInterface(ServiceManager.getService("activity"));
@@ -273,7 +273,7 @@
         }
         final long identity = Binder.clearCallingIdentity();
         try {
-            mPackageManager.grantRuntimePermission(packageName, permission, userId);
+            mPermissionManager.grantRuntimePermission(packageName, permission, userId);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -289,7 +289,7 @@
         }
         final long identity = Binder.clearCallingIdentity();
         try {
-            mPackageManager.revokeRuntimePermission(packageName, permission, userId);
+            mPermissionManager.revokeRuntimePermission(packageName, permission, userId);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 341fc26..7a013f1 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4665,10 +4665,10 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve an
-     * {@link android.telephony.ims.RcsManager}.
+     * {@link android.telephony.ims.RcsMessageManager}.
      * @hide
      */
-    public static final String TELEPHONY_RCS_SERVICE = "ircs";
+    public static final String TELEPHONY_RCS_MESSAGE_SERVICE = "ircsmessage";
 
      /**
      * Use with {@link #getSystemService(String)} to retrieve an
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 277e41d..904bd16 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -32,7 +32,6 @@
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageMoveObserver;
 import android.content.pm.IPackageStatsObserver;
-import android.content.pm.IOnPermissionsChangeListener;
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.KeySet;
@@ -95,36 +94,7 @@
 
     @UnsupportedAppUsage
     ProviderInfo getProviderInfo(in ComponentName className, int flags, int userId);
-
-    @UnsupportedAppUsage
-    int checkPermission(String permName, String pkgName, int userId);
-
-    int checkUidPermission(String permName, int uid);
-
-    @UnsupportedAppUsage
-    void grantRuntimePermission(String packageName, String permissionName, int userId);
-
-    void revokeRuntimePermission(String packageName, String permissionName, int userId);
-
-    void resetRuntimePermissions();
-
-    int getPermissionFlags(String permissionName, String packageName, int userId);
-
-    void updatePermissionFlags(String permissionName, String packageName, int flagMask,
-            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId);
-
-    void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId);
-
-    List<String> getWhitelistedRestrictedPermissions(String packageName, int flags,
-            int userId);
-
-    boolean addWhitelistedRestrictedPermission(String packageName, String permission,
-            int whitelistFlags, int userId);
-
-    boolean removeWhitelistedRestrictedPermission(String packageName, String permission,
-            int whitelistFlags, int userId);
-
-    boolean shouldShowRequestPermissionRationale(String permissionName,
+    boolean shouldShowRequestPermissionRationale(String permName,
             String packageName, int userId);
 
     boolean isProtectedBroadcast(String actionName);
@@ -659,8 +629,6 @@
     boolean isPackageSignedByKeySet(String packageName, in KeySet ks);
     boolean isPackageSignedByKeySetExactly(String packageName, in KeySet ks);
 
-    void addOnPermissionsChangeListener(in IOnPermissionsChangeListener listener);
-    void removeOnPermissionsChangeListener(in IOnPermissionsChangeListener listener);
     void grantDefaultPermissionsToEnabledCarrierApps(in String[] packageNames, int userId);
     void grantDefaultPermissionsToEnabledImsServices(in String[] packageNames, int userId);
     void grantDefaultPermissionsToEnabledTelephonyDataServices(
@@ -757,6 +725,10 @@
     // The following binder interfaces have been moved to IPermissionManager
     //
     //------------------------------------------------------------------------
+
+    //------------------------------------------------------------------------
+    // We need to keep these in IPackageManager for app compatibility
+    //------------------------------------------------------------------------
     @UnsupportedAppUsage
     String[] getAppOpPermissionPackages(String permissionName);
 
@@ -771,4 +743,17 @@
 
     @UnsupportedAppUsage
     void removePermission(String name);
+
+    @UnsupportedAppUsage
+    int checkPermission(String permName, String pkgName, int userId);
+
+    @UnsupportedAppUsage
+    void grantRuntimePermission(String packageName, String permissionName, int userId);
+
+    //------------------------------------------------------------------------
+    // We need to keep these in IPackageManager for convenience in splitting
+    // out the permission manager. This should be cleaned up, but, will require
+    // a large change that modifies many repos.
+    //------------------------------------------------------------------------
+    int checkUidPermission(String permName, int uid);
 }
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 1c1d709..e21d4c41 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -32,13 +32,10 @@
 import android.util.ArraySet;
 import android.util.SparseArray;
 
-import com.android.internal.util.function.TriFunction;
-
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
-import java.util.function.BiFunction;
 import java.util.function.Consumer;
 
 /**
@@ -86,32 +83,6 @@
         void onPackageRemoved(@NonNull String packageName, int uid);
     }
 
-    /** Interface to override permission checks via composition */
-    public interface CheckPermissionDelegate {
-        /**
-         * Allows overriding check permission behavior.
-         *
-         * @param permName The permission to check.
-         * @param pkgName The package for which to check.
-         * @param userId The user for which to check.
-         * @param superImpl The super implementation.
-         * @return The check permission result.
-         */
-        int checkPermission(String permName, String pkgName, int userId,
-                TriFunction<String, String, Integer, Integer> superImpl);
-
-        /**
-         * Allows overriding check UID permission behavior.
-         *
-         * @param permName The permission to check.
-         * @param uid The UID for which to check.
-         * @param superImpl The super implementation.
-         * @return The check permission result.
-         */
-        int checkUidPermission(String permName, int uid,
-                BiFunction<String, Integer, Integer> superImpl);
-    }
-
     /**
      * Provider for package names.
      */
@@ -464,26 +435,6 @@
     public abstract boolean wasPackageEverLaunched(String packageName, int userId);
 
     /**
-     * Grants a runtime permission
-     * @param packageName The package name.
-     * @param name The name of the permission.
-     * @param userId The userId for which to grant the permission.
-     * @param overridePolicy If true, grant this permission even if it is fixed by policy.
-     */
-    public abstract void grantRuntimePermission(String packageName, String name, int userId,
-            boolean overridePolicy);
-
-    /**
-     * Revokes a runtime permission
-     * @param packageName The package name.
-     * @param name The name of the permission.
-     * @param userId The userId for which to revoke the permission.
-     * @param overridePolicy If true, revoke this permission even if it is fixed by policy.
-     */
-    public abstract void revokeRuntimePermission(String packageName, String name, int userId,
-            boolean overridePolicy);
-
-    /**
      * Retrieve the official name associated with a uid. This name is
      * guaranteed to never change, though it is possible for the underlying
      * uid to be changed. That is, if you are storing information about
@@ -668,6 +619,12 @@
     public abstract @Nullable PackageParser.Package getPackage(@NonNull String packageName);
 
     /**
+     * Returns a package for the given UID. If the UID is part of a shared user ID, one
+     * of the packages will be chosen to be returned.
+     */
+    public abstract @Nullable PackageParser.Package getPackage(int uid);
+
+    /**
      * Returns a list without a change observer.
      *
      * @see #getPackageList(PackageListObserver)
@@ -742,17 +699,6 @@
     public abstract boolean filterAppAccess(
             @Nullable PackageParser.Package pkg, int callingUid, int userId);
 
-    /*
-     * NOTE: The following methods are temporary until permissions are extracted from
-     * the package manager into a component specifically for handling permissions.
-     */
-    /** Returns the flags for the given permission. */
-    public abstract @Nullable int getPermissionFlagsTEMP(@NonNull String permName,
-            @NonNull String packageName, int userId);
-    /** Updates the flags for the given permission. */
-    public abstract void updatePermissionFlagsTEMP(@NonNull String permName,
-            @NonNull String packageName, int flagMask, int flagValues, int userId);
-
     /** Returns whether the given package was signed by the platform */
     public abstract boolean isPlatformSigned(String pkg);
 
@@ -782,20 +728,6 @@
             @PackageParser.SigningDetails.CertCapabilities int capability);
 
     /**
-     * Get the delegate to influence permission checking.
-     *
-     * @return The delegate instance or null to clear.
-     */
-    public abstract @Nullable CheckPermissionDelegate getCheckPermissionDelegate();
-
-    /**
-     * Set a delegate to influence permission checking.
-     *
-     * @param delegate A delegate instance or null to clear.
-     */
-    public abstract void setCheckPermissionDelegate(@Nullable CheckPermissionDelegate delegate);
-
-    /**
      * Get appIds of all available apps which specified android:sharedUserId in the manifest.
      *
      * @return a SparseArray mapping from appId to it's sharedUserId.
@@ -1006,4 +938,26 @@
      * the settings have been written.
      */
     public abstract void writeSettings(boolean async);
+
+    /**
+     * Writes all permission settings for the given set of users to disk. If {@code async}
+     * is {@code true}, the settings are written at some point in the future. Otherwise,
+     * the call blocks until the settings have been written.
+     */
+    public abstract void writePermissionSettings(@NonNull @UserIdInt int[] userIds, boolean async);
+
+    /**
+     * Returns the target SDK for the given UID. Will return {@code 0} if there is no
+     * package associated with the UID or if the package has not been installed for
+     * the user. Will return the highest target SDK if the UID references packages with
+     * a shared user id.
+     */
+    public abstract int getTargetSdk(int uid);
+
+    /**
+     * Returns {@code true} if the caller is the installer of record for the given package.
+     * Otherwise, {@code false}.
+     */
+    public abstract boolean isCallerInstallerOfRecord(
+            @NonNull PackageParser.Package pkg, int callingUid);
 }
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 794be9e..b72544c 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -856,7 +856,8 @@
             stack.push(id);
             try {
                 if (file.endsWith(".xml")) {
-                    if (file.startsWith("res/color/")) {
+                    final String typeName = getResourceTypeName(id);
+                    if (typeName != null && typeName.equals("color")) {
                         dr = loadColorOrXmlDrawable(wrapper, value, id, density, file);
                     } else {
                         dr = loadXmlDrawable(wrapper, value, id, density, file);
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index ae421a4..87c7118 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -48,6 +48,7 @@
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -481,17 +482,39 @@
      * For example, given an incoming template matching B, and the currently
      * active merge set [A,B], we'd return a new template that primarily matches
      * A, but also matches B.
+     * TODO: remove and use {@link #normalize(NetworkTemplate, List)}.
      */
     @UnsupportedAppUsage
     public static NetworkTemplate normalize(NetworkTemplate template, String[] merged) {
-        if (template.isMatchRuleMobile() && ArrayUtils.contains(merged, template.mSubscriberId)) {
-            // Requested template subscriber is part of the merge group; return
-            // a template that matches all merged subscribers.
-            return new NetworkTemplate(template.mMatchRule, merged[0], merged,
-                    template.mNetworkId);
-        } else {
-            return template;
+        return normalize(template, Arrays.<String[]>asList(merged));
+    }
+
+    /**
+     * Examine the given template and normalize if it refers to a "merged"
+     * mobile subscriber. We pick the "lowest" merged subscriber as the primary
+     * for key purposes, and expand the template to match all other merged
+     * subscribers.
+     *
+     * There can be multiple merged subscriberIds for multi-SIM devices.
+     *
+     * <p>
+     * For example, given an incoming template matching B, and the currently
+     * active merge set [A,B], we'd return a new template that primarily matches
+     * A, but also matches B.
+     */
+    public static NetworkTemplate normalize(NetworkTemplate template, List<String[]> mergedList) {
+        if (!template.isMatchRuleMobile()) return template;
+
+        for (String[] merged : mergedList) {
+            if (ArrayUtils.contains(merged, template.mSubscriberId)) {
+                // Requested template subscriber is part of the merge group; return
+                // a template that matches all merged subscribers.
+                return new NetworkTemplate(template.mMatchRule, merged[0], merged,
+                        template.mNetworkId);
+            }
         }
+
+        return template;
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/content/pm/IOnPermissionsChangeListener.aidl b/core/java/android/permission/IOnPermissionsChangeListener.aidl
similarity index 96%
rename from core/java/android/content/pm/IOnPermissionsChangeListener.aidl
rename to core/java/android/permission/IOnPermissionsChangeListener.aidl
index 7791b50..cc52a72 100644
--- a/core/java/android/content/pm/IOnPermissionsChangeListener.aidl
+++ b/core/java/android/permission/IOnPermissionsChangeListener.aidl
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.content.pm;
+package android.permission;
 
 /**
  * Listener for changes in the permissions for installed packages.
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 3b69b12..d31cee0 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -19,6 +19,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
+import android.permission.IOnPermissionsChangeListener;
 
 /**
  * Interface to communicate directly with the permission manager service.
@@ -39,4 +40,34 @@
     boolean addPermission(in PermissionInfo info, boolean async);
 
     void removePermission(String name);
+
+    int getPermissionFlags(String permName, String packageName, int userId);
+
+    void updatePermissionFlags(String permName, String packageName, int flagMask,
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId);
+
+    void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId);
+
+    int checkPermission(String permName, String pkgName, int userId);
+
+    int checkUidPermission(String permName, int uid);
+
+    void addOnPermissionsChangeListener(in IOnPermissionsChangeListener listener);
+
+    void removeOnPermissionsChangeListener(in IOnPermissionsChangeListener listener);
+
+    List<String> getWhitelistedRestrictedPermissions(String packageName,
+            int flags, int userId);
+
+    boolean addWhitelistedRestrictedPermission(String packageName, String permName,
+            int flags, int userId);
+
+    boolean removeWhitelistedRestrictedPermission(String packageName, String permName,
+            int flags, int userId);
+
+    void grantRuntimePermission(String packageName, String permName, int userId);
+
+    void revokeRuntimePermission(String packageName, String permName, int userId);
+
+    void resetRuntimePermissions();
 }
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 182a2ff..42816c0 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -43,6 +43,14 @@
 @SystemApi
 @SystemService(Context.PERMISSION_SERVICE)
 public final class PermissionManager {
+    /** @hide */
+    public static final String KILL_APP_REASON_PERMISSIONS_REVOKED =
+            "permissions revoked";
+    /** @hide */
+    public static final String KILL_APP_REASON_GIDS_CHANGED =
+            "permission grant or revoke changed gids";
+
+
     /**
      * {@link android.content.pm.PackageParser} needs access without having a {@link Context}.
      *
diff --git a/core/java/android/permission/PermissionManagerInternal.java b/core/java/android/permission/PermissionManagerInternal.java
index 7167431..3134ec0 100644
--- a/core/java/android/permission/PermissionManagerInternal.java
+++ b/core/java/android/permission/PermissionManagerInternal.java
@@ -21,6 +21,10 @@
 import android.annotation.UserIdInt;
 import android.os.UserHandle;
 
+import com.android.internal.util.function.TriFunction;
+
+import java.util.function.BiFunction;
+
 /**
  * Internal interfaces to be used by other components within the system server.
  *
@@ -46,6 +50,33 @@
                 @UserIdInt int userId);
     }
 
+    /** Interface to override permission checks via composition */
+    public interface CheckPermissionDelegate {
+        /**
+         * Checks whether the given package has been granted the specified permission.
+         *
+         * @return If the package has the permission, PERMISSION_GRANTED is
+         * returned.  If it does not have the permission, PERMISSION_DENIED
+         * is returned.
+         *
+         * @see android.content.pm.PackageManager#checkPermission(String, String)
+         */
+        int checkPermission(String permName, String pkgName, int userId,
+                TriFunction<String, String, Integer, Integer> superImpl);
+
+        /**
+        /**
+         * Checks whether the given uid has been granted the specified permission.
+         *
+         * @return If the package has the permission, PERMISSION_GRANTED is
+         * returned.  If it does not have the permission, PERMISSION_DENIED
+         * is returned.
+         *
+         */
+        int checkUidPermission(String permName, int uid,
+                BiFunction<String, Integer, Integer> superImpl);
+    }
+
     /**
      * Get the state of the runtime permissions as xml file.
      *
diff --git a/core/java/android/util/proto/ProtoInputStream.java b/core/java/android/util/proto/ProtoInputStream.java
index c290dff..24e7d2b 100644
--- a/core/java/android/util/proto/ProtoInputStream.java
+++ b/core/java/android/util/proto/ProtoInputStream.java
@@ -253,12 +253,14 @@
     }
 
     /**
-     * Attempt to guess the next field. If there is a match, the field data will be ready to read.
-     * If there is no match, nextField will need to be called to get the field number
+     * Reads the tag of the next field from the stream. If previous field value was not read, its
+     * data will be skipped over. If {@code fieldId} matches the next field ID, the field data will
+     * be ready to read. If it does not match, {@link #nextField()} or {@link #nextField(long)} will
+     * need to be called again before the field data can be read.
      *
      * @return true if fieldId matches the next field, false if not
      */
-    public boolean isNextField(long fieldId) throws IOException {
+    public boolean nextField(long fieldId) throws IOException {
         if (nextField() == (int) fieldId) {
             return true;
         }
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 4b92968..203b087 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -38,7 +38,6 @@
 import android.text.style.ClickableSpan;
 import android.util.LongSparseArray;
 import android.util.Slog;
-import android.view.View.AttachInfo;
 import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeIdManager;
@@ -903,38 +902,6 @@
                 }
             }
         }
-
-        if (spec != null) {
-            AttachInfo attachInfo = mViewRootImpl.mAttachInfo;
-            if (attachInfo.mDisplay == null) {
-                return;
-            }
-
-            final float scale = attachInfo.mApplicationScale * spec.scale;
-
-            Rect visibleWinFrame = mTempRect1;
-            visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX);
-            visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY);
-            visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale);
-            visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale);
-
-            attachInfo.mDisplay.getRealSize(mTempPoint);
-            final int displayWidth = mTempPoint.x;
-            final int displayHeight = mTempPoint.y;
-
-            Rect visibleDisplayFrame = mTempRect2;
-            visibleDisplayFrame.set(0, 0, displayWidth, displayHeight);
-
-            if (!visibleWinFrame.intersect(visibleDisplayFrame)) {
-                // If there's no intersection with display, set visibleWinFrame empty.
-                visibleDisplayFrame.setEmpty();
-            }
-
-            if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top,
-                    boundsInScreen.right, boundsInScreen.bottom)) {
-                info.setVisibleToUser(false);
-            }
-        }
     }
 
     private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
@@ -948,8 +915,10 @@
         try {
             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
             adjustBoundsInScreenIfNeeded(infos);
-            applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
+            // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
+            // then impact the visibility result, we need to adjust visibility before apply scale.
             adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
+            applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
             callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
             if (infos != null) {
                 infos.clear();
@@ -967,8 +936,10 @@
         try {
             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
             adjustBoundsInScreenIfNeeded(info);
-            applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
+            // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
+            // then impact the visibility result, we need to adjust visibility before apply scale.
             adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
+            applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
             callback.setFindAccessibilityNodeInfoResult(info, interactionId);
         } catch (RemoteException re) {
                 /* ignore - the other side will time out */
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 5b3dbb1..d474b4d 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -1688,6 +1688,11 @@
      * Instead it represents the result of {@link View#getParentForAccessibility()},
      * which returns the closest ancestor where {@link View#isImportantForAccessibility()} is true.
      * So this method is not reliable.
+     * <p>
+     * When magnification is enabled, the bounds in parent are also scaled up by magnification
+     * scale. For example, it returns Rect(20, 20, 200, 200) for original bounds
+     * Rect(10, 10, 100, 100), when the magnification scale is 2.
+     * <p/>
      *
      * @param outBounds The output node bounds.
      * @deprecated Use {@link #getBoundsInScreen(Rect)} instead.
@@ -1725,6 +1730,12 @@
 
     /**
      * Gets the node bounds in screen coordinates.
+     * <p>
+     * When magnification is enabled, the bounds in screen are scaled up by magnification scale
+     * and the positions are also adjusted according to the offset of magnification viewport.
+     * For example, it returns Rect(-180, -180, 0, 0) for original bounds Rect(10, 10, 100, 100),
+     * when the magnification scale is 2 and offsets for X and Y are both 200.
+     * <p/>
      *
      * @param outBounds The output node bounds.
      */
@@ -1861,6 +1872,12 @@
 
     /**
      * Gets whether this node is visible to the user.
+     * <p>
+     * Between {@link Build.VERSION_CODES#JELLY_BEAN API 16} and
+     * {@link Build.VERSION_CODES#Q API 29}, this method may incorrectly return false when
+     * magnification is enabled. On other versions, a node is considered visible even if it is not
+     * on the screen because magnification is active.
+     * </p>
      *
      * @return Whether the node is visible to the user.
      */
diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java
index c7c2924..c8929e9 100644
--- a/core/java/com/android/internal/infra/AndroidFuture.java
+++ b/core/java/com/android/internal/infra/AndroidFuture.java
@@ -115,6 +115,20 @@
         }
     }
 
+    /**
+     * Create a completed future with the given value.
+     *
+     * @param value the value for the completed future
+     * @param <U> the type of the value
+     * @return the completed future
+     */
+    @NonNull
+    public static <U> AndroidFuture<U> completedFuture(U value) {
+        AndroidFuture<U> future = new AndroidFuture<>();
+        future.complete(value);
+        return future;
+    }
+
     @Override
     public boolean complete(@Nullable T value) {
         boolean changed = super.complete(value);
diff --git a/core/java/com/android/internal/infra/ThrottledRunnable.java b/core/java/com/android/internal/infra/ThrottledRunnable.java
new file mode 100644
index 0000000..9846fa9
--- /dev/null
+++ b/core/java/com/android/internal/infra/ThrottledRunnable.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 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.internal.infra;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * A throttled runnable that can wrap around a runnable and throttle calls to its run().
+ *
+ * The throttling logic makes sure that the original runnable will be called only after the
+ * specified interval passes since the last actual call. The first call in a while (after the
+ * specified interval passes since the last actual call) will always result in the original runnable
+ * being called immediately, and then subsequent calls will start to be throttled. It is guaranteed
+ * that any call to this throttled runnable will always result in the original runnable being called
+ * afterwards, within the specified interval.
+ */
+public class ThrottledRunnable implements Runnable {
+
+    @NonNull
+    private final Handler mHandler;
+    private final long mIntervalMillis;
+    @NonNull
+    private final Runnable mRunnable;
+
+    @NonNull
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private long mScheduledUptimeMillis;
+
+    public ThrottledRunnable(@NonNull Handler handler, long intervalMillis,
+            @NonNull Runnable runnable) {
+        mHandler = handler;
+        mIntervalMillis = intervalMillis;
+        mRunnable = runnable;
+    }
+
+    @Override
+    public void run() {
+        synchronized (mLock) {
+            if (mHandler.hasCallbacks(mRunnable)) {
+                // We have a scheduled runnable.
+                return;
+            }
+            long currentUptimeMillis = SystemClock.uptimeMillis();
+            if (mScheduledUptimeMillis == 0
+                    || currentUptimeMillis > mScheduledUptimeMillis + mIntervalMillis) {
+                // First time in a while, schedule immediately.
+                mScheduledUptimeMillis = currentUptimeMillis;
+            } else {
+                // We were scheduled not long ago, so schedule with delay for throttling.
+                mScheduledUptimeMillis = mScheduledUptimeMillis + mIntervalMillis;
+            }
+            mHandler.postAtTime(mRunnable, mScheduledUptimeMillis);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
index adf7692..52172cf 100644
--- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
+++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
@@ -36,13 +36,16 @@
         }
 
         // Radius that should be used in case top or bottom aren't defined.
-        float defaultRadius = resources.getDimension(R.dimen.rounded_corner_radius);
+        float defaultRadius = resources.getDimension(R.dimen.rounded_corner_radius)
+                - resources.getDimension(R.dimen.rounded_corner_radius_adjustment);
 
-        float topRadius = resources.getDimension(R.dimen.rounded_corner_radius_top);
+        float topRadius = resources.getDimension(R.dimen.rounded_corner_radius_top)
+                - resources.getDimension(R.dimen.rounded_corner_radius_top_adjustment);
         if (topRadius == 0f) {
             topRadius = defaultRadius;
         }
-        float bottomRadius = resources.getDimension(R.dimen.rounded_corner_radius_bottom);
+        float bottomRadius = resources.getDimension(R.dimen.rounded_corner_radius_bottom)
+                - resources.getDimension(R.dimen.rounded_corner_radius_bottom_adjustment);
         if (bottomRadius == 0f) {
             bottomRadius = defaultRadius;
         }
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 5363ef9..039bc4d 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -90,6 +90,24 @@
         orientation. If zero, the value of rounded_corner_radius is used. -->
     <dimen name="rounded_corner_radius_bottom">0dp</dimen>
 
+    <!-- Default adjustment for the software rounded corners since corners are not perfectly
+        round. This value is used when retrieving the "radius" of the rounded corner in cases
+        where the exact bezier curve cannot be retrieved.  This value will be subtracted from
+        rounded_corner_radius to more accurately provide a "radius" for the rounded corner. -->
+    <dimen name="rounded_corner_radius_adjustment">0px</dimen>
+    <!-- Top adjustment for the software rounded corners since corners are not perfectly
+        round.  This value is used when retrieving the "radius" of the top rounded corner in cases
+        where the exact bezier curve cannot be retrieved.  This value will be subtracted from
+        rounded_corner_radius_top to more accurately provide a "radius" for the top rounded corners.
+         -->
+    <dimen name="rounded_corner_radius_top_adjustment">0px</dimen>
+    <!-- Bottom adjustment for the software rounded corners since corners are not perfectly
+        round.  This value is used when retrieving the "radius" of the bottom rounded corner in
+        cases where the exact bezier curve cannot be retrieved.  This value will be subtracted from
+        rounded_corner_radius_bottom to more accurately provide a "radius" for the bottom rounded
+        corners. -->
+    <dimen name="rounded_corner_radius_bottom_adjustment">0px</dimen>
+
     <!-- Width of the window of the divider bar used to resize docked stacks. -->
     <dimen name="docked_stack_divider_thickness">48dp</dimen>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 498e60e..b3b1b9f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3708,6 +3708,9 @@
   <java-symbol type="dimen" name="rounded_corner_radius" />
   <java-symbol type="dimen" name="rounded_corner_radius_top" />
   <java-symbol type="dimen" name="rounded_corner_radius_bottom" />
+  <java-symbol type="dimen" name="rounded_corner_radius_adjustment" />
+  <java-symbol type="dimen" name="rounded_corner_radius_top_adjustment" />
+  <java-symbol type="dimen" name="rounded_corner_radius_bottom_adjustment" />
   <java-symbol type="bool" name="config_supportsRoundedCornersOnWindows" />
 
   <java-symbol type="string" name="config_defaultModuleMetadataProvider" />
diff --git a/core/tests/coretests/res/values/overlayable_icons_test.xml b/core/tests/coretests/res/values/overlayable_icons_test.xml
deleted file mode 100644
index 6503f3e..0000000
--- a/core/tests/coretests/res/values/overlayable_icons_test.xml
+++ /dev/null
@@ -1,86 +0,0 @@
-<!--
-   Copyright (C) 2019 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.
--->
-<resources>
-  <!-- overlayable_icons references all of the drawables in this package
-       that are being overlayed by resource overlays. If you remove/rename
-       any of these resources, you must also change the resource overlay icons.-->
-  <array name="overlayable_icons">
-    <item>@*android:drawable/ic_audio_alarm</item>
-    <item>@*android:drawable/ic_audio_alarm_mute</item>
-    <item>@*android:drawable/ic_battery_80_24dp</item>
-    <item>@*android:drawable/ic_bluetooth_share_icon</item>
-    <item>@*android:drawable/ic_bt_headphones_a2dp</item>
-    <item>@*android:drawable/ic_bt_headset_hfp</item>
-    <item>@*android:drawable/ic_bt_hearing_aid</item>
-    <item>@*android:drawable/ic_bt_laptop</item>
-    <item>@*android:drawable/ic_bt_misc_hid</item>
-    <item>@*android:drawable/ic_bt_network_pan</item>
-    <item>@*android:drawable/ic_bt_pointing_hid</item>
-    <item>@*android:drawable/ic_corp_badge</item>
-    <item>@*android:drawable/ic_expand_more</item>
-    <item>@*android:drawable/ic_faster_emergency</item>
-    <item>@*android:drawable/ic_file_copy</item>
-    <item>@*android:drawable/ic_lock</item>
-    <item>@*android:drawable/ic_lock_bugreport</item>
-    <item>@*android:drawable/ic_lock_open</item>
-    <item>@*android:drawable/ic_lock_power_off</item>
-    <item>@*android:drawable/ic_lockscreen_ime</item>
-    <item>@*android:drawable/ic_mode_edit</item>
-    <item>@*android:drawable/ic_notifications_alerted</item>
-    <item>@*android:drawable/ic_phone</item>
-    <item>@*android:drawable/ic_qs_airplane</item>
-    <item>@*android:drawable/ic_qs_auto_rotate</item>
-    <item>@*android:drawable/ic_qs_battery_saver</item>
-    <item>@*android:drawable/ic_qs_bluetooth</item>
-    <item>@*android:drawable/ic_qs_dnd</item>
-    <item>@*android:drawable/ic_qs_flashlight</item>
-    <item>@*android:drawable/ic_qs_night_display_on</item>
-    <item>@*android:drawable/ic_qs_ui_mode_night</item>
-    <item>@*android:drawable/ic_restart</item>
-    <item>@*android:drawable/ic_screenshot</item>
-    <item>@*android:drawable/ic_settings_bluetooth</item>
-    <item>@*android:drawable/ic_signal_cellular_0_4_bar</item>
-    <item>@*android:drawable/ic_signal_cellular_0_5_bar</item>
-    <item>@*android:drawable/ic_signal_cellular_1_4_bar</item>
-    <item>@*android:drawable/ic_signal_cellular_1_5_bar</item>
-    <item>@*android:drawable/ic_signal_cellular_2_4_bar</item>
-    <item>@*android:drawable/ic_signal_cellular_2_5_bar</item>
-    <item>@*android:drawable/ic_signal_cellular_3_4_bar</item>
-    <item>@*android:drawable/ic_signal_cellular_3_5_bar</item>
-    <item>@*android:drawable/ic_signal_cellular_4_4_bar</item>
-    <item>@*android:drawable/ic_signal_cellular_4_5_bar</item>
-    <item>@*android:drawable/ic_signal_cellular_5_5_bar</item>
-    <item>@*android:drawable/ic_signal_location</item>
-    <item>@*android:drawable/ic_wifi_signal_0</item>
-    <item>@*android:drawable/ic_wifi_signal_1</item>
-    <item>@*android:drawable/ic_wifi_signal_2</item>
-    <item>@*android:drawable/ic_wifi_signal_3</item>
-    <item>@*android:drawable/ic_wifi_signal_4</item>
-    <item>@*android:drawable/perm_group_activity_recognition</item>
-    <item>@*android:drawable/perm_group_aural</item>
-    <item>@*android:drawable/perm_group_calendar</item>
-    <item>@*android:drawable/perm_group_call_log</item>
-    <item>@*android:drawable/perm_group_camera</item>
-    <item>@*android:drawable/perm_group_contacts</item>
-    <item>@*android:drawable/perm_group_location</item>
-    <item>@*android:drawable/perm_group_microphone</item>
-    <item>@*android:drawable/perm_group_phone_calls</item>
-    <item>@*android:drawable/perm_group_sensors</item>
-    <item>@*android:drawable/perm_group_sms</item>
-    <item>@*android:drawable/perm_group_storage</item>
-    <item>@*android:drawable/perm_group_visual</item>
-  </array>
-</resources>
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index a480784..3809bc4 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -501,15 +501,7 @@
         return NULL;
     }
 
-    int colorFormat = getColorFormat(env, params);
 
-    std::vector<sp<IMemory> > frames;
-    status_t err = retriever->getFrameAtIndex(&frames, frameIndex, numFrames, colorFormat);
-    if (err != OK || frames.size() == 0) {
-        jniThrowException(env,
-                "java/lang/IllegalStateException", "No frames from retriever");
-        return NULL;
-    }
     jobject arrayList = env->NewObject(fields.arrayListClazz, fields.arrayListInit);
     if (arrayList == NULL) {
         jniThrowException(env,
@@ -517,18 +509,29 @@
         return NULL;
     }
 
+    int colorFormat = getColorFormat(env, params);
     SkColorType outColorType = setOutColorType(env, colorFormat, params);
-
-    for (size_t i = 0; i < frames.size(); i++) {
-        if (frames[i] == NULL || frames[i]->pointer() == NULL) {
+    size_t i = 0;
+    for (; i < numFrames; i++) {
+        sp<IMemory> frame = retriever->getFrameAtIndex(frameIndex + i, colorFormat);
+        if (frame == NULL || frame->pointer() == NULL) {
             ALOGE("video frame at index %zu is a NULL pointer", frameIndex + i);
-            continue;
+            break;
         }
-        VideoFrame *videoFrame = static_cast<VideoFrame *>(frames[i]->pointer());
+        VideoFrame *videoFrame = static_cast<VideoFrame *>(frame->pointer());
         jobject bitmapObj = getBitmapFromVideoFrame(env, videoFrame, -1, -1, outColorType);
         env->CallBooleanMethod(arrayList, fields.arrayListAdd, bitmapObj);
         env->DeleteLocalRef(bitmapObj);
     }
+
+    if (i == 0) {
+        env->DeleteLocalRef(arrayList);
+
+        jniThrowException(env,
+                "java/lang/IllegalStateException", "No frames from retriever");
+        return NULL;
+    }
+
     return arrayList;
 }
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java
index 0f4cf21..0421c3b 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.TypedArray;
+import android.os.Build;
 import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.view.Display;
@@ -122,7 +123,7 @@
                         new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.CURRENT);
             });
 
-            if (longPressIntentString != null) {
+            if (longPressIntentString != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
                 final Intent longPressIntent = Intent.parseUri(longPressIntentString,
                         Intent.URI_INTENT_SCHEME);
                 setOnLongClickListener(v -> {
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
index 095e2e9..05a41e6 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.car;
 
 import android.content.Context;
-import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -75,40 +74,13 @@
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (mStatusBarWindowTouchListener != null) {
-            boolean shouldConsumeEvent = shouldConsumeNotificationButtonEvent(ev);
             // Forward touch events to the status bar window so it can drag
             // windows if required (Notification shade)
             mStatusBarWindowTouchListener.onTouch(this, ev);
-            // return true if child views should not receive this event.
-            if (shouldConsumeEvent) {
-                return true;
-            }
         }
         return super.onInterceptTouchEvent(ev);
     }
 
-    /**
-     * If the motion event is over top of the notification button while the notification
-     * panel is open, we need the statusbar touch listeners handle the event instead of the button.
-     * Since the statusbar listener will trigger a close of the notification panel before the
-     * any button click events are fired this will prevent reopening the panel.
-     *
-     * Note: we can't use requestDisallowInterceptTouchEvent because the gesture detector will
-     * always receive the ACTION_DOWN and thus think a longpress happened if no other events are
-     * received
-     *
-     * @return true if the notification button should not receive the event
-     */
-    private boolean shouldConsumeNotificationButtonEvent(MotionEvent ev) {
-        if (mNotificationsButton == null || !mCarStatusBar.isNotificationPanelOpen()) {
-            return false;
-        }
-        Rect notificationButtonLocation = new Rect();
-        mNotificationsButton.getHitRect(notificationButtonLocation);
-        return notificationButtonLocation.contains((int) ev.getX(), (int) ev.getY());
-    }
-
-
     void setStatusBar(CarStatusBar carStatusBar) {
         mCarStatusBar = carStatusBar;
     }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
index 9de4ef5..fc5e791 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.TypedArray;
+import android.os.Build;
 import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -114,7 +115,7 @@
         }
 
         try {
-            if (mLongIntent != null) {
+            if (mLongIntent != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
                 final Intent intent = Intent.parseUri(mLongIntent, Intent.URI_INTENT_SCHEME);
                 setOnLongClickListener(v -> {
                     try {
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 71b9dc3..a452bae 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -1248,13 +1248,6 @@
             setNotificationViewClipBounds((int) event2.getRawY());
             return true;
         }
-
-        @Override
-        public void onLongPress(MotionEvent e) {
-            mClosingVelocity = DEFAULT_FLING_VELOCITY;
-            close();
-            super.onLongPress(e);
-        }
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index b15ea98..23e29493 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -18,11 +18,11 @@
 
 import android.content.Context;
 import android.net.NetworkTemplate;
-import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import com.android.internal.util.ArrayUtils;
 /**
  * Utils class for data usage
  */
@@ -34,19 +34,25 @@
      */
     public static NetworkTemplate getMobileTemplate(Context context, int subId) {
         final TelephonyManager telephonyManager = context.getSystemService(
-                TelephonyManager.class).createForSubscriptionId(subId);
+                TelephonyManager.class);
         final SubscriptionManager subscriptionManager = context.getSystemService(
                 SubscriptionManager.class);
-        final SubscriptionInfo info = subscriptionManager.getActiveSubscriptionInfo(subId);
         final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
                 telephonyManager.getSubscriberId(subId));
 
-        if (info == null) {
+        if (!subscriptionManager.isActiveSubId(subId)) {
             Log.i(TAG, "Subscription is not active: " + subId);
             return mobileAll;
         }
 
-        // Use old API to build networkTemplate
-        return NetworkTemplate.normalize(mobileAll, telephonyManager.getMergedSubscriberIds());
+        final String[] mergedSubscriberIds = telephonyManager.createForSubscriptionId(subId)
+                .getMergedSubscriberIdsFromGroup();
+
+        if (ArrayUtils.isEmpty(mergedSubscriberIds)) {
+            Log.i(TAG, "mergedSubscriberIds is null.");
+            return mobileAll;
+        }
+
+        return NetworkTemplate.normalize(mobileAll, mergedSubscriberIds);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
index 821c0b3..5cae611 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
@@ -37,13 +38,13 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-import java.util.ArrayList;
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
 public class DataUsageUtilsTest {
 
     private static final int SUB_ID = 1;
+    private static final int SUB_ID_2 = 2;
     private static final String SUBSCRIBER_ID = "Test Subscriber";
     private static final String SUBSCRIBER_ID_2 = "Test Subscriber 2";
 
@@ -66,21 +67,16 @@
 
         mContext = spy(RuntimeEnvironment.application);
         when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
-        when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
         when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
         when(mTelephonyManager.getSubscriberId(SUB_ID)).thenReturn(SUBSCRIBER_ID);
-        when(mTelephonyManager.getMergedSubscriberIds()).thenReturn(
-                new String[]{SUBSCRIBER_ID, SUBSCRIBER_ID_2});
-
-        mInfos = new ArrayList<>();
-        mInfos.add(mInfo1);
-        mInfos.add(mInfo2);
-        when(mSubscriptionManager.getSubscriptionsInGroup(mParcelUuid)).thenReturn(mInfos);
+        when(mTelephonyManager.getSubscriberId(SUB_ID_2)).thenReturn(SUBSCRIBER_ID_2);
+        when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
+        when(mSubscriptionManager.isActiveSubId(anyInt())).thenReturn(true);
     }
 
     @Test
     public void getMobileTemplate_infoNull_returnMobileAll() {
-        when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(null);
+        when(mSubscriptionManager.isActiveSubId(SUB_ID)).thenReturn(false);
 
         final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
         assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID)).isTrue();
@@ -88,9 +84,23 @@
     }
 
     @Test
-    public void getMobileTemplate_infoExisted_returnMobileMerged() {
+    public void getMobileTemplate_groupUuidNull_returnMobileAll() {
+        when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
+        when(mInfo1.getGroupUuid()).thenReturn(null);
+        when(mTelephonyManager.getMergedSubscriberIdsFromGroup())
+                .thenReturn(new String[] {SUBSCRIBER_ID});
+
+        final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
+        assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID)).isTrue();
+        assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID_2)).isFalse();
+    }
+
+    @Test
+    public void getMobileTemplate_groupUuidExist_returnMobileMerged() {
         when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
         when(mInfo1.getGroupUuid()).thenReturn(mParcelUuid);
+        when(mTelephonyManager.getMergedSubscriberIdsFromGroup())
+                .thenReturn(new String[] {SUBSCRIBER_ID, SUBSCRIBER_ID_2});
 
         final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
         assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID)).isTrue();
diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml
index b23824e..8547bc8 100644
--- a/packages/SystemUI/res-keyguard/values-bs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml
@@ -22,7 +22,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="3171996292755059205">"Zaključavanje tastature"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3420548423949593123">"Upišite PIN"</string>
-    <string name="keyguard_password_enter_puk_code" msgid="670683628782925409">"Upišite PUK kôd za SIM karticu i novi PIN"</string>
+    <string name="keyguard_password_enter_puk_code" msgid="670683628782925409">"Upišite PUK za SIM i novi PIN kôd"</string>
     <string name="keyguard_password_enter_puk_prompt" msgid="3747778500166059332">"PUK kôd za SIM karticu"</string>
     <string name="keyguard_password_enter_pin_prompt" msgid="8188243197504453830">"Novi PIN za SIM karticu"</string>
     <string name="keyguard_password_entry_touch_hint" msgid="5790410752696806482"><font size="17">"Dodirnite da upišete lozinku"</font></string>
@@ -54,7 +54,7 @@
     <string name="keyguard_accessibility_pin_area" msgid="703175752097279029">"Prostor za PIN"</string>
     <string name="keyguard_accessibility_password" msgid="7695303207740941101">"Lozinka uređaja"</string>
     <string name="keyguard_accessibility_sim_pin_area" msgid="912702510825058921">"Prostor za PIN za SIM karticu"</string>
-    <string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Prostor za PUK kôd za SIM karticu"</string>
+    <string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Prostor za PUK za SIM"</string>
     <string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Naredni alarm je podešen za <xliff:g id="ALARM">%1$s</xliff:g>"</string>
     <string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Izbriši"</string>
     <string name="disable_carrier_button_text" msgid="6914341927421916114">"Onemogući eSIM karticu"</string>
@@ -116,7 +116,7 @@
       <item quantity="other">PUK kôd za SIM karticu je netačan. Imate još <xliff:g id="NUMBER_1">%d</xliff:g> pokušaja prije nego što SIM kartica postane trajno neupotrebljiva.</item>
     </plurals>
     <string name="kg_password_pin_failed" msgid="8769990811451236223">"Korištenje PIN-a za SIM karticu nije uspjelo!"</string>
-    <string name="kg_password_puk_failed" msgid="1331621440873439974">"Korištenje PUK koda za SIM karticu nije uspjelo!"</string>
+    <string name="kg_password_puk_failed" msgid="1331621440873439974">"Korištenje PUK-a za SIM nije uspjelo!"</string>
     <string name="kg_pin_accepted" msgid="7637293533973802143">"Kôd je prihvaćen"</string>
     <string name="keyguard_carrier_default" msgid="4274828292998453695">"Nema mreže."</string>
     <string name="accessibility_ime_switch_button" msgid="2695096475319405612">"Promjena načina unosa"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml
index 1d42f1f..e4b37d0 100644
--- a/packages/SystemUI/res-keyguard/values-sq/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml
@@ -38,7 +38,7 @@
     <string name="keyguard_plugged_in" msgid="3161102098900158923">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Po karikohet"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="3684592786276709342">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Po karikohet me shpejtësi"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="509533586841478405">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Po karikohet ngadalë"</string>
-    <string name="keyguard_low_battery" msgid="9218432555787624490">"Lidh ngarkuesin."</string>
+    <string name="keyguard_low_battery" msgid="9218432555787624490">"Lidh karikuesin."</string>
     <string name="keyguard_instructions_when_pattern_disabled" msgid="8566679946700751371">"Shtyp \"Meny\" për të shkyçur."</string>
     <string name="keyguard_network_locked_message" msgid="6743537524631420759">"Rrjeti është i kyçur"</string>
     <string name="keyguard_missing_sim_message_short" msgid="6327533369959764518">"Nuk ka kartë SIM"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 7f85ec0..3d297ae 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -464,7 +464,7 @@
     <string name="battery_saver_notification_title" msgid="8614079794522291840">"تم تفعيل ميزة توفير شحن البطارية"</string>
     <string name="battery_saver_notification_text" msgid="820318788126672692">"لخفض مستوى الأداء وبيانات الخلفية"</string>
     <string name="battery_saver_notification_action_text" msgid="132118784269455533">"إيقاف ميزة توفير شحن البطارية"</string>
-    <string name="media_projection_dialog_text" msgid="8585357687598538511">"أثناء التسجيل أو الإرسال، يمكن لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> تسجيل أي معلومات حساسة يتم عرضها على الشاشة أو تشغيلها من جهازك، بما فيها المعلومات الحساسة مثل الصوت الذي تشغّله وكلمات المرور ومعلومات الدفع والصور والرسائل."</string>
+    <string name="media_projection_dialog_text" msgid="8585357687598538511">"أثناء التسجيل أو البث، يمكن لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> تسجيل أي معلومات حسّاسة يتم عرضها على الشاشة أو تشغيلها من جهازك، بما فيها المعلومات الحسّاسة مثل المقاطع الصوتية وكلمات المرور ومعلومات الدفع والصور والرسائل."</string>
     <string name="media_projection_dialog_service_text" msgid="3075544489835858258">"أثناء التسجيل أو الإرسال، يمكن للخدمة التي تقدّم هذه الوظيفة تسجيل أي معلومات حساسة يتم عرضها على الشاشة أو تشغيلها من جهازك، بما فيها المعلومات الحساسة مثل الصوت الذي تشغّله وكلمات المرور ومعلومات الدفع والصور والرسائل."</string>
     <string name="media_projection_dialog_title" msgid="8124184308671641248">"عرض معلومات حسّاسة أثناء الإرسال/التسجيل"</string>
     <string name="media_projection_remember_text" msgid="3103510882172746752">"عدم الإظهار مرة أخرى"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index a5f9989..7b2d793 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -871,7 +871,7 @@
     <string name="notification_channel_storage" msgid="3077205683020695313">"Memoria"</string>
     <string name="notification_channel_hints" msgid="7323870212489152689">"Aholkuak"</string>
     <string name="instant_apps" msgid="6647570248119804907">"Zuzeneko aplikazioak"</string>
-    <string name="instant_apps_title" msgid="8738419517367449783">"<xliff:g id="APP">%1$s</xliff:g> exekutatzen ari da"</string>
+    <string name="instant_apps_title" msgid="8738419517367449783">"<xliff:g id="APP">%1$s</xliff:g> abian da"</string>
     <string name="instant_apps_message" msgid="1183313016396018086">"Ezer instalatu gabe ireki da aplikazioa."</string>
     <string name="instant_apps_message_with_help" msgid="6179830437630729747">"Ezer instalatu gabe ireki da aplikazioa. Sakatu informazio gehiago lortzeko."</string>
     <string name="app_info" msgid="6856026610594615344">"Aplikazioari buruzko informazioa"</string>
@@ -888,7 +888,7 @@
     <string name="qs_dnd_until" msgid="3469471136280079874">"<xliff:g id="ID_1">%s</xliff:g> arte"</string>
     <string name="qs_dnd_keep" msgid="1825009164681928736">"Utzi bere horretan"</string>
     <string name="qs_dnd_replace" msgid="8019520786644276623">"Ordeztu"</string>
-    <string name="running_foreground_services_title" msgid="381024150898615683">"Aplikazioak exekutatzen ari dira atzeko planoan"</string>
+    <string name="running_foreground_services_title" msgid="381024150898615683">"Aplikazioak abian dira atzeko planoan"</string>
     <string name="running_foreground_services_msg" msgid="6326247670075574355">"Sakatu bateria eta datuen erabilerari buruzko xehetasunak ikusteko"</string>
     <string name="mobile_data_disable_title" msgid="1068272097382942231">"Datu-konexioa desaktibatu nahi duzu?"</string>
     <string name="mobile_data_disable_message" msgid="4756541658791493506">"<xliff:g id="CARRIER">%s</xliff:g> erabilita ezingo dituzu erabili datuak edo Internet. Wifi-sare baten bidez soilik konektatu ahal izango zara Internetera."</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index ef29f9b..263fe8a 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -484,7 +484,7 @@
     <string name="monitoring_title_device_owned" msgid="1652495295941959815">"डिवाइस प्रबंधन"</string>
     <string name="monitoring_title_profile_owned" msgid="6790109874733501487">"प्रोफ़ाइल को मॉनीटर करना"</string>
     <string name="monitoring_title" msgid="169206259253048106">"नेटवर्क को मॉनीटर करना"</string>
-    <string name="monitoring_subtitle_vpn" msgid="876537538087857300">"VPN"</string>
+    <string name="monitoring_subtitle_vpn" msgid="876537538087857300">"वीपीएन"</string>
     <string name="monitoring_subtitle_network_logging" msgid="3341264304793193386">"नेटवर्क लॉगिंग"</string>
     <string name="monitoring_subtitle_ca_certificate" msgid="3874151893894355988">"CA प्रमाणपत्र"</string>
     <string name="disable_vpn" msgid="4435534311510272506">"VPN अक्षम करें"</string>
@@ -513,7 +513,7 @@
     <string name="monitoring_description_network_logging" msgid="7223505523384076027">"आपके एडमिन ने नेटवर्क लॉग करना चालू कर दिया है, जो आपके डिवाइस पर ट्रैफ़िक की निगरानी करता है.\n\nज़्यादा जानकारी के लिए अपने एडमिन से संपर्क करें."</string>
     <string name="monitoring_description_vpn" msgid="4445150119515393526">"आपने किसी ऐप को VPN कनेक्‍शन सेट करने की अनुमति दी है.\n\nयह ऐप ईमेल, ऐप्‍स और सुरक्षित वेबसाइटों सहित आपके डिवाइस और नेटवर्क की गतिविधि की निगरानी कर सकता है."</string>
     <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> आपकी वर्क प्रोफ़ाइल को प्रबंधित करता है.\n\n आपका एडमिन ईमेल, ऐप्लिकेशन और वेबसाइटों सहित आपकी नेटवर्क गतिविधि की निगरानी कर सकता है.\n\nऔर जानकारी के लिए अपने एडमिन से संपर्क करें.\n\nआप ऐसे VPN से भी कनेक्‍ट हैं, जो आपकी नेटवर्क गतिविधि की निगरानी कर सकता है."</string>
-    <string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
+    <string name="legacy_vpn_name" msgid="6604123105765737830">"वीपीएन"</string>
     <string name="monitoring_description_app" msgid="1828472472674709532">"आप <xliff:g id="APPLICATION">%1$s</xliff:g> से कनेक्ट हैं, जो ईमेल, ऐप्लिकेशन और वेबसाइटों सहित आपकी नेटवर्क गतिविधि की निगरानी कर सकता है."</string>
     <string name="monitoring_description_app_personal" msgid="484599052118316268">"आप <xliff:g id="APPLICATION">%1$s</xliff:g> से कनेक्‍ट हैं, जो ईमेल, ऐप्‍स और वेबसाइटों सहित आपकी व्‍यक्‍तिगत नेटवर्क गतिविधि की निगरानी कर सकता है."</string>
     <string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"आप <xliff:g id="APPLICATION">%1$s</xliff:g> से कनेक्‍ट हैं, जो ईमेल, ऐप्लिकेशन और वेबसाइट सहित आपकी व्‍यक्‍तिगत नेटवर्क गतिविधि को मॉनिटर कर सकता है."</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 6d614b1..3156a6f 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -130,7 +130,7 @@
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Դեմքի ճանաչում…"</string>
     <string name="accessibility_face_dialog_face_icon" msgid="2658119009870383490">"Դեմքի պատկերակ"</string>
     <string name="accessibility_compatibility_zoom_button" msgid="8461115318742350699">"Համատեղելիության խոշորացման կոճակը:"</string>
-    <string name="accessibility_compatibility_zoom_example" msgid="4220687294564945780">"Դիտափոխել փոքրից ավելի մեծ էկրան:"</string>
+    <string name="accessibility_compatibility_zoom_example" msgid="4220687294564945780">"Մասշտաբը մեծացնել փոքրից ավելի մեծ էկրան:"</string>
     <string name="accessibility_bluetooth_connected" msgid="2707027633242983370">"Bluetooth-ը միացված է:"</string>
     <string name="accessibility_bluetooth_disconnected" msgid="7416648669976870175">"Bluetooth-ն անջատված է:"</string>
     <string name="accessibility_no_battery" msgid="358343022352820946">"Մարտկոց չկա:"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 59b6eb3..19b2b99 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -89,7 +89,7 @@
     <string name="screenrecord_share_label" msgid="4197867360204019389">"Bagikan"</string>
     <string name="screenrecord_delete_label" msgid="7893716870917824013">"Hapus"</string>
     <string name="screenrecord_cancel_success" msgid="7768976011702614782">"Rekaman layar dibatalkan"</string>
-    <string name="screenrecord_save_message" msgid="4733982661301846778">"Rekaman layar disimpan, tap untuk melihat"</string>
+    <string name="screenrecord_save_message" msgid="4733982661301846778">"Rekaman layar disimpan, ketuk untuk melihat"</string>
     <string name="screenrecord_delete_description" msgid="5743190456090354585">"Rekaman layar dihapus"</string>
     <string name="screenrecord_delete_error" msgid="8154904464563560282">"Error saat menghapus rekaman layar"</string>
     <string name="screenrecord_permission_error" msgid="1526755299469001000">"Gagal mendapatkan izin"</string>
@@ -836,7 +836,7 @@
     <string name="pip_phone_dismiss_hint" msgid="6351678169095923899">"Tarik ke bawah untuk menutup"</string>
     <string name="pip_menu_title" msgid="4707292089961887657">"Menu"</string>
     <string name="pip_notification_title" msgid="3204024940158161322">"<xliff:g id="NAME">%s</xliff:g> adalah picture-in-picture"</string>
-    <string name="pip_notification_message" msgid="5619512781514343311">"Jika Anda tidak ingin <xliff:g id="NAME">%s</xliff:g> menggunakan fitur ini, tap untuk membuka setelan dan menonaktifkannya."</string>
+    <string name="pip_notification_message" msgid="5619512781514343311">"Jika Anda tidak ingin <xliff:g id="NAME">%s</xliff:g> menggunakan fitur ini, ketuk untuk membuka setelan dan menonaktifkannya."</string>
     <string name="pip_play" msgid="1417176722760265888">"Putar"</string>
     <string name="pip_pause" msgid="8881063404466476571">"Jeda"</string>
     <string name="pip_skip_to_next" msgid="1948440006726306284">"Lewati ke berikutnya"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 1058d74..08816c7 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -29,9 +29,9 @@
     <string name="battery_low_percent_format_hybrid" msgid="6838677459286775617">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> të mbetura, rreth <xliff:g id="TIME">%2$s</xliff:g> të mbetura bazuar në përdorimin tënd"</string>
     <string name="battery_low_percent_format_hybrid_short" msgid="9025795469949145586">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> të mbetura, rreth <xliff:g id="TIME">%2$s</xliff:g> të mbetura"</string>
     <string name="battery_low_percent_format_saver_started" msgid="7879389868952879166">"Ka mbetur edhe <xliff:g id="PERCENTAGE">%s</xliff:g>. \"Kursyesi i baterisë\" është i aktivizuar."</string>
-    <string name="invalid_charger" msgid="2741987096648693172">"Nuk mund të ngarkohet përmes USB-së. Përdor ngarkuesin që ke marrë me pajisjen."</string>
+    <string name="invalid_charger" msgid="2741987096648693172">"Nuk mund të karikohet përmes USB-së. Përdor karikuesin që ke marrë me pajisjen."</string>
     <string name="invalid_charger_title" msgid="2836102177577255404">"Nuk mund të ngarkohet përmes USB-së"</string>
-    <string name="invalid_charger_text" msgid="6480624964117840005">"Përdor ngarkuesin që ke marrë me pajisjen"</string>
+    <string name="invalid_charger_text" msgid="6480624964117840005">"Përdor karikuesin që ke marrë me pajisjen"</string>
     <string name="battery_low_why" msgid="4553600287639198111">"Cilësimet"</string>
     <string name="battery_saver_confirmation_title" msgid="2052100465684817154">"Të aktivizohet \"Kursyesi i baterisë\"?"</string>
     <string name="battery_saver_confirmation_title_generic" msgid="2090922638411744540">"Rreth \"Kursyesit të baterisë\""</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 0e74993..56521f6 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -33,7 +33,7 @@
     <string name="invalid_charger_title" msgid="2836102177577255404">"Не вдається зарядити через USB"</string>
     <string name="invalid_charger_text" msgid="6480624964117840005">"Використовуйте зарядний пристрій, який входить у комплект пристрою"</string>
     <string name="battery_low_why" msgid="4553600287639198111">"Налаштування"</string>
-    <string name="battery_saver_confirmation_title" msgid="2052100465684817154">"Увімкнути режим економії заряду акумулятора?"</string>
+    <string name="battery_saver_confirmation_title" msgid="2052100465684817154">"Увімкнути режим енергозбереження?"</string>
     <string name="battery_saver_confirmation_title_generic" msgid="2090922638411744540">"Про режим енергозбереження"</string>
     <string name="battery_saver_confirmation_ok" msgid="7507968430447930257">"Увімкнути"</string>
     <string name="battery_saver_start_action" msgid="8187820911065797519">"Увімкнути режим економії заряду акумулятора"</string>
@@ -455,7 +455,7 @@
     <string name="user_remove_user_title" msgid="4681256956076895559">"Видалити користувача?"</string>
     <string name="user_remove_user_message" msgid="1453218013959498039">"Усі додатки й дані цього користувача буде видалено."</string>
     <string name="user_remove_user_remove" msgid="7479275741742178297">"Видалити"</string>
-    <string name="battery_saver_notification_title" msgid="8614079794522291840">"Режим економії заряду акумулятора ввімкнено"</string>
+    <string name="battery_saver_notification_title" msgid="8614079794522291840">"Режим енергозбереження ввімкнено"</string>
     <string name="battery_saver_notification_text" msgid="820318788126672692">"Знижується продуктивність і обмежуються фонові дані"</string>
     <string name="battery_saver_notification_action_text" msgid="132118784269455533">"Вимкнути режим економії заряду акумулятора"</string>
     <string name="media_projection_dialog_text" msgid="8585357687598538511">"Під час запису або трансляції додаток <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> може фіксувати будь-яку конфіденційну інформацію (зокрема, аудіо, паролі, платіжну інформацію, фотографії та повідомлення), яка з\'являється на екрані або відтворюється на пристрої."</string>
@@ -913,7 +913,7 @@
     <string name="auto_saver_title" msgid="1217959994732964228">"Торкніться, щоб увімкнути автоматичний режим економії заряду акумулятора"</string>
     <string name="auto_saver_text" msgid="2563289953551438248">"Вмикати, коли заряд акумулятора закінчується"</string>
     <string name="no_auto_saver_action" msgid="8086002101711328500">"Ні, дякую"</string>
-    <string name="auto_saver_enabled_title" msgid="6726474226058316862">"Автоматичний режим економії заряду акумулятора ввімкнено"</string>
+    <string name="auto_saver_enabled_title" msgid="6726474226058316862">"Автоматичний перехід у режим енергозбереження ввімкнено"</string>
     <string name="auto_saver_enabled_text" msgid="874711029884777579">"Режим економії заряду акумулятора вмикається автоматично, коли рівень заряду нижчий за <xliff:g id="PERCENTAGE">%d</xliff:g>%%."</string>
     <string name="open_saver_setting_action" msgid="8314624730997322529">"Налаштування"</string>
     <string name="auto_saver_okay_action" msgid="2701221740227683650">"OK"</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 5b9ee1c..dcb134e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -20,6 +20,7 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityManager.TaskDescription;
+import android.app.TaskInfo;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -75,7 +76,7 @@
 
         private int mHashCode;
 
-        public TaskKey(ActivityManager.RecentTaskInfo t) {
+        public TaskKey(TaskInfo t) {
             ComponentName sourceComponent = t.origActivity != null
                     // Activity alias if there is one
                     ? t.origActivity
@@ -226,6 +227,17 @@
         // Do nothing
     }
 
+    /**
+     * Creates a task object from the provided task info
+     */
+    public static Task from(TaskKey taskKey, TaskInfo taskInfo, boolean isLocked) {
+        ActivityManager.TaskDescription td = taskInfo.taskDescription;
+        return new Task(taskKey,
+                td != null ? td.getPrimaryColor() : 0,
+                td != null ? td.getBackgroundColor() : 0,
+                taskInfo.supportsSplitScreenMultiWindow, isLocked, td, taskInfo.topActivity);
+    }
+
     public Task(TaskKey key) {
         this.key = key;
         this.taskDescription = new TaskDescription();
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 506813b..328116d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -30,10 +30,12 @@
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
 import android.app.AppGlobals;
 import android.app.IAssistDataReceiver;
+import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.ActivityType;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -500,4 +502,12 @@
                 PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
                 || freeformDevOption);
     }
+
+    /**
+     * Returns true if the running task represents the home task
+     */
+    public static boolean isHomeTask(RunningTaskInfo info) {
+        return info.configuration.windowConfiguration.getActivityType()
+                == WindowConfiguration.ACTIVITY_TYPE_HOME;
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 7fbe5db..cc7863c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -49,6 +49,15 @@
     public static final String NAV_BAR_MODE_GESTURAL_OVERLAY =
             WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
 
+    // Action sent by a system app to switch to gesture nav
+    public static final String ACTION_ENABLE_GESTURE_NAV =
+            "com.android.systemui.ENABLE_GESTURE_NAV";
+    // Action for the intent to receive the result
+    public static final String ACTION_ENABLE_GESTURE_NAV_RESULT =
+            "com.android.systemui.action.ENABLE_GESTURE_NAV_RESULT";
+    // Extra containing the pending intent to receive the result
+    public static final String EXTRA_RESULT_INTENT = "com.android.systemui.EXTRA_RESULT_INTENT";
+
     // Overview is disabled, either because the device is in lock task mode, or because the device
     // policy has disabled the feature
     public static final int SYSUI_STATE_SCREEN_PINNING = 1 << 0;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 05e14a7..2eb93d3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2203,6 +2203,15 @@
         if (DEBUG) Log.d(TAG, "handleKeyguardBouncerChanged(" + bouncer + ")");
         boolean isBouncer = (bouncer == 1);
         mBouncer = isBouncer;
+
+        if (isBouncer) {
+            // If the bouncer is shown, always clear this flag. This can happen in the following
+            // situations: 1) Default camera with SHOW_WHEN_LOCKED is not chosen yet. 2) Secure
+            // camera requests dismiss keyguard (tapping on photos for example). When these happen,
+            // face auth should resume.
+            mSecureCameraLaunched = false;
+        }
+
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -2649,6 +2658,7 @@
             pw.println("    strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
             pw.println("    trustManaged=" + getUserTrustIsManaged(userId));
             pw.println("    enabledByUser=" + mFaceSettingEnabledForUser);
+            pw.println("    mSecureCameraLaunched=" + mSecureCameraLaunched);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index 6fb6467..67dfdca 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
@@ -67,8 +67,8 @@
             Sensor.TYPE_LIGHT,
             Sensor.TYPE_ROTATION_VECTOR,
     };
-    private static final String FALSING_REMAIN_LOCKED = "falsing_failure_after_attempts";
-    private static final String FALSING_SUCCESS = "falsing_success_after_attempts";
+    public static final String FALSING_REMAIN_LOCKED = "falsing_failure_after_attempts";
+    public static final String FALSING_SUCCESS = "falsing_success_after_attempts";
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
index 19c46e4..00f35aa 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.classifier.brightline;
 
+import static com.android.systemui.classifier.FalsingManagerImpl.FALSING_REMAIN_LOCKED;
+import static com.android.systemui.classifier.FalsingManagerImpl.FALSING_SUCCESS;
+
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
@@ -24,6 +27,7 @@
 import android.util.Log;
 import android.view.MotionEvent;
 
+import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.plugins.FalsingManager;
 
@@ -44,6 +48,8 @@
     private final SensorManager mSensorManager;
     private final FalsingDataProvider mDataProvider;
     private boolean mSessionStarted;
+    private MetricsLogger mMetricsLogger;
+    private int mIsFalseTouchCalls;
 
     private final ExecutorService mBackgroundExecutor = Executors.newSingleThreadExecutor();
 
@@ -64,6 +70,7 @@
             SensorManager sensorManager) {
         mDataProvider = falsingDataProvider;
         mSensorManager = sensorManager;
+        mMetricsLogger = new MetricsLogger();
         mClassifiers = new ArrayList<>();
         DistanceClassifier distanceClassifier = new DistanceClassifier(mDataProvider);
         ProximityClassifier proximityClassifier = new ProximityClassifier(distanceClassifier,
@@ -111,6 +118,10 @@
             unregisterSensors();
             mDataProvider.onSessionEnd();
             mClassifiers.forEach(FalsingClassifier::onSessionEnded);
+            if (mIsFalseTouchCalls != 0) {
+                mMetricsLogger.histogram(FALSING_REMAIN_LOCKED, mIsFalseTouchCalls);
+                mIsFalseTouchCalls = 0;
+            }
         }
     }
 
@@ -157,6 +168,10 @@
 
     @Override
     public void onSucccessfulUnlock() {
+        if (mIsFalseTouchCalls != 0) {
+            mMetricsLogger.histogram(FALSING_SUCCESS, mIsFalseTouchCalls);
+            mIsFalseTouchCalls = 0;
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 70ee752..c9050d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -398,7 +398,7 @@
             }
         }
 
-        if (!riv.isAttachedToWindow()) {
+        if (riv != null && !riv.isAttachedToWindow()) {
             // the remoteInput isn't attached to the window anymore :/ Let's focus on the expanded
             // one instead if it's available
             riv = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 05d26b0..bc7174d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -261,6 +261,8 @@
                 biometricSourceType);
         if (unlockAllowed) {
             startWakeAndUnlock(biometricSourceType);
+        } else {
+            Log.d(TAG, "onBiometricAuthenticated aborted by bypass controller");
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 21a22ec..d6f8a60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -346,10 +346,10 @@
     }
 
     /**
-     * {@link #show(boolean)} was called but we're not showing yet.
+     * {@link #show(boolean)} was called but we're not showing yet, or being dragged.
      */
-    public boolean willShowSoon() {
-        return mShowingSoon;
+    public boolean inTransit() {
+        return mShowingSoon || mExpansion != EXPANSION_HIDDEN;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index d7deedc..c88b22b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -21,11 +21,11 @@
 import android.hardware.biometrics.BiometricSourceType
 import android.hardware.face.FaceManager
 import android.provider.Settings
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.tuner.TunerService
+import java.io.PrintWriter
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -62,18 +62,15 @@
         }
 
     @Inject
-    constructor(context: Context, tunerService: TunerService,
-                statusBarStateController: StatusBarStateController,
-                lockscreenUserManager: NotificationLockscreenUserManager) {
+    constructor(
+        context: Context,
+        tunerService: TunerService,
+        statusBarStateController: StatusBarStateController,
+        lockscreenUserManager: NotificationLockscreenUserManager
+    ) {
         unlockMethodCache = UnlockMethodCache.getInstance(context)
         this.statusBarStateController = statusBarStateController
-        statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
-            override fun onStateChanged(newState: Int) {
-                if (newState != StatusBarState.KEYGUARD) {
-                    pendingUnlockType = null;
-                }
-            }
-        })
+
         if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
             return
         }
@@ -82,16 +79,19 @@
             return
         }
 
+        statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
+            override fun onStateChanged(newState: Int) {
+                if (newState != StatusBarState.KEYGUARD) {
+                    pendingUnlockType = null
+                }
+            }
+        })
+
         val dismissByDefault = if (context.resources.getBoolean(
-                com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
-        tunerService.addTunable(
-                object : TunerService.Tunable {
-                        override fun onTuningChanged(key: String?, newValue: String?) {
-                                bypassEnabled = Settings.Secure.getIntForUser(
-                                        context.contentResolver,
-                                        Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD,
-                                        dismissByDefault,
-                                        KeyguardUpdateMonitor.getCurrentUser()) != 0
+                        com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
+        tunerService.addTunable(object : TunerService.Tunable {
+            override fun onTuningChanged(key: String?, newValue: String?) {
+                bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
             }
         }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
         lockscreenUserManager.addUserChangedListener { pendingUnlockType = null }
@@ -155,4 +155,16 @@
     fun onStartedGoingToSleep() {
         pendingUnlockType = null
     }
+
+    fun dump(pw: PrintWriter) {
+        pw.println("KeyguardBypassController:")
+        pw.print("  pendingUnlockType: "); pw.println(pendingUnlockType)
+        pw.print("  bypassEnabled: "); pw.println(bypassEnabled)
+        pw.print("  canBypass: "); pw.println(canBypass())
+        pw.print("  bouncerShowing: "); pw.println(bouncerShowing)
+        pw.print("  isPulseExpanding: "); pw.println(isPulseExpanding)
+        pw.print("  launchingAffordance: "); pw.println(launchingAffordance)
+        pw.print("  qSExpanded: "); pw.println(qSExpanded)
+        pw.print("  bouncerShowing: "); pw.println(bouncerShowing)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
index 4d7cf27..64fef55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
@@ -16,17 +16,26 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.app.Activity.RESULT_CANCELED;
+import static android.app.Activity.RESULT_OK;
+import static android.app.admin.DevicePolicyManager.STATE_USER_UNMANAGED;
 import static android.content.Intent.ACTION_OVERLAY_CHANGED;
 import static android.content.Intent.ACTION_PREFERRED_ACTIVITY_CHANGED;
+import static android.content.pm.PackageManager.FEATURE_DEVICE_ADMIN;
 import static android.os.UserHandle.USER_CURRENT;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
 
+import static com.android.systemui.shared.system.QuickStepContract.ACTION_ENABLE_GESTURE_NAV;
+import static com.android.systemui.shared.system.QuickStepContract.ACTION_ENABLE_GESTURE_NAV_RESULT;
+import static com.android.systemui.shared.system.QuickStepContract.EXTRA_RESULT_INTENT;
+
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -40,6 +49,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.text.TextUtils;
@@ -157,6 +167,8 @@
                 }
             };
 
+    private BroadcastReceiver mEnableGestureNavReceiver;
+
     @Inject
     public NavigationModeController(Context context,
             DeviceProvisionedController deviceProvisionedController,
@@ -177,6 +189,7 @@
         IntentFilter preferredActivityFilter = new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED);
         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, preferredActivityFilter, null,
                 null);
+
         // We are only interested in launcher changes, so keeping track of the current default.
         mLastDefaultLauncher = getDefaultLauncherPackageName(mContext);
 
@@ -187,6 +200,82 @@
         deferGesturalNavOverlayIfNecessary();
     }
 
+    private void removeEnableGestureNavListener() {
+        if (mEnableGestureNavReceiver != null) {
+            if (DEBUG) {
+                Log.d(TAG, "mEnableGestureNavReceiver unregistered");
+            }
+            mContext.unregisterReceiver(mEnableGestureNavReceiver);
+            mEnableGestureNavReceiver = null;
+        }
+    }
+
+    private boolean setGestureModeOverlayForMainLauncher() {
+        removeEnableGestureNavListener();
+        if (getCurrentInteractionMode(mCurrentUserContext) == NAV_BAR_MODE_GESTURAL) {
+            // Already in gesture mode
+            return true;
+        }
+        final Boolean supported = isGestureNavSupportedByDefaultLauncher(mCurrentUserContext);
+        if (supported == null || supported) {
+            Log.d(TAG, "Switching system navigation to full-gesture mode:"
+                    + " defaultLauncher="
+                    + getDefaultLauncherPackageName(mCurrentUserContext)
+                    + " contextUser="
+                    + mCurrentUserContext.getUserId());
+
+            setModeOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT);
+            return true;
+        } else {
+            Log.e(TAG, "Gesture nav is not supported for defaultLauncher="
+                    + getDefaultLauncherPackageName(mCurrentUserContext));
+            return false;
+        }
+    }
+
+    private boolean enableGestureNav(Intent intent) {
+        if (!(intent.getParcelableExtra(EXTRA_RESULT_INTENT) instanceof PendingIntent)) {
+            Log.e(TAG, "No callback pending intent was attached");
+            return false;
+        }
+
+        PendingIntent callback = intent.getParcelableExtra(EXTRA_RESULT_INTENT);
+        Intent callbackIntent = callback.getIntent();
+        if (callbackIntent == null
+                || !ACTION_ENABLE_GESTURE_NAV_RESULT.equals(callbackIntent.getAction())) {
+            Log.e(TAG, "Invalid callback intent");
+            return false;
+        }
+        String callerPackage = callback.getCreatorPackage();
+        UserHandle callerUser = callback.getCreatorUserHandle();
+
+        DevicePolicyManager dpm = mCurrentUserContext.getSystemService(DevicePolicyManager.class);
+        ComponentName ownerComponent = dpm.getDeviceOwnerComponentOnCallingUser();
+
+        if (ownerComponent != null) {
+            // Verify that the caller is the owner component
+            if (!ownerComponent.getPackageName().equals(callerPackage)
+                    || !mCurrentUserContext.getUser().equals(callerUser)) {
+                Log.e(TAG, "Callback must be from the device owner");
+                return false;
+            }
+        } else {
+            UserHandle callerParent = mCurrentUserContext.getSystemService(UserManager.class)
+                    .getProfileParent(callerUser);
+            if (callerParent == null || !callerParent.equals(mCurrentUserContext.getUser())) {
+                Log.e(TAG, "Callback must be from a managed user");
+                return false;
+            }
+            ComponentName profileOwner = dpm.getProfileOwnerAsUser(callerUser);
+            if (profileOwner == null || !profileOwner.getPackageName().equals(callerPackage)) {
+                Log.e(TAG, "Callback must be from the profile owner");
+                return false;
+            }
+        }
+
+        return setGestureModeOverlayForMainLauncher();
+    }
+
     public void updateCurrentInteractionMode(boolean notify) {
         mCurrentUserContext = getCurrentUserContext();
         int mode = getCurrentInteractionMode(mCurrentUserContext);
@@ -245,6 +334,10 @@
         }
     }
 
+    private boolean supportsDeviceAdmin() {
+        return mContext.getPackageManager().hasSystemFeature(FEATURE_DEVICE_ADMIN);
+    }
+
     private void deferGesturalNavOverlayIfNecessary() {
         final int userId = mDeviceProvisionedController.getCurrentUser();
         mRestoreGesturalNavBarMode.put(userId, false);
@@ -255,6 +348,7 @@
                 Log.d(TAG, "deferGesturalNavOverlayIfNecessary: device is provisioned and user is "
                         + "setup");
             }
+            removeEnableGestureNavListener();
             return;
         }
 
@@ -270,6 +364,7 @@
                 Log.d(TAG, "deferGesturalNavOverlayIfNecessary: no default gestural overlay, "
                         + "default=" + defaultOverlays);
             }
+            removeEnableGestureNavListener();
             return;
         }
 
@@ -277,6 +372,24 @@
         // provisioned
         setModeOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY, USER_CURRENT);
         mRestoreGesturalNavBarMode.put(userId, true);
+
+        if (supportsDeviceAdmin() && mEnableGestureNavReceiver == null) {
+            mEnableGestureNavReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (DEBUG) {
+                        Log.d(TAG, "ACTION_ENABLE_GESTURE_NAV");
+                    }
+                    setResultCode(enableGestureNav(intent) ? RESULT_OK : RESULT_CANCELED);
+                }
+            };
+            // Register for all users so that we can get managed users as well
+            mContext.registerReceiverAsUser(mEnableGestureNavReceiver, UserHandle.ALL,
+                    new IntentFilter(ACTION_ENABLE_GESTURE_NAV), null, null);
+            if (DEBUG) {
+                Log.d(TAG, "mEnableGestureNavReceiver registered");
+            }
+        }
         if (DEBUG) {
             Log.d(TAG, "deferGesturalNavOverlayIfNecessary: setting to 3 button mode");
         }
@@ -290,7 +403,15 @@
         final int userId = mDeviceProvisionedController.getCurrentUser();
         if (mRestoreGesturalNavBarMode.get(userId)) {
             // Restore the gestural state if necessary
-            setModeOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT);
+            if (!supportsDeviceAdmin()
+                    || mCurrentUserContext.getSystemService(DevicePolicyManager.class)
+                    .getUserProvisioningState() == STATE_USER_UNMANAGED) {
+                setGestureModeOverlayForMainLauncher();
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "Not restoring to gesture nav for managed user");
+                }
+            }
             mRestoreGesturalNavBarMode.put(userId, false);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index aebf1c8..40ebe58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -2386,6 +2386,10 @@
             mLightBarController.dump(fd, pw, args);
         }
 
+        if (mKeyguardBypassController != null) {
+            mKeyguardBypassController.dump(pw);
+        }
+
         if (mKeyguardUpdateMonitor != null) {
             mKeyguardUpdateMonitor.dump(fd, pw, args);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index d678182..65be708 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -720,7 +720,7 @@
      * animation didn't finish yet.
      */
     public boolean bouncerIsOrWillBeShowing() {
-        return mBouncer.isShowing() || mBouncer.willShowSoon();
+        return mBouncer.isShowing() || mBouncer.inTransit();
     }
 
     public boolean isFullscreenBouncer() {
diff --git a/packages/SystemUI/tests/res/values/overlayable_icons_test.xml b/packages/SystemUI/tests/res/values/overlayable_icons_test.xml
deleted file mode 100644
index 24cd8cb..0000000
--- a/packages/SystemUI/tests/res/values/overlayable_icons_test.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-<!--
-   Copyright (C) 2019 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.
--->
-<resources>
-  <!-- overlayable_icons references all of the drawables in this package
-       that are being overlayed by resource overlays. If you remove/rename
-       any of these resources, you must also change the resource overlay icons.-->
-  <array name="overlayable_icons">
-    <item>@drawable/ic_alarm</item>
-    <item>@drawable/ic_alarm_dim</item>
-    <item>@drawable/ic_arrow_back</item>
-    <item>@drawable/ic_bluetooth_connected</item>
-    <item>@drawable/ic_brightness_thumb</item>
-    <item>@drawable/ic_camera</item>
-    <item>@drawable/ic_cast</item>
-    <item>@drawable/ic_cast_connected</item>
-    <item>@drawable/ic_close_white</item>
-    <item>@drawable/ic_data_saver</item>
-    <item>@drawable/ic_data_saver_off</item>
-    <item>@drawable/ic_drag_handle</item>
-    <item>@drawable/ic_headset</item>
-    <item>@drawable/ic_headset_mic</item>
-    <item>@drawable/ic_hotspot</item>
-    <item>@drawable/ic_info</item>
-    <item>@drawable/ic_info_outline</item>
-    <item>@drawable/ic_invert_colors</item>
-    <item>@drawable/ic_location</item>
-    <item>@drawable/ic_lockscreen_ime</item>
-    <item>@drawable/ic_notifications_alert</item>
-    <item>@drawable/ic_notifications_silence</item>
-    <item>@drawable/ic_power_low</item>
-    <item>@drawable/ic_power_saver</item>
-    <item>@drawable/ic_qs_bluetooth_connecting</item>
-    <item>@drawable/ic_qs_cancel</item>
-    <item>@drawable/ic_qs_no_sim</item>
-    <item>@drawable/ic_qs_wifi_0</item>
-    <item>@drawable/ic_qs_wifi_1</item>
-    <item>@drawable/ic_qs_wifi_2</item>
-    <item>@drawable/ic_qs_wifi_3</item>
-    <item>@drawable/ic_qs_wifi_4</item>
-    <item>@drawable/ic_qs_wifi_disconnected</item>
-    <item>@drawable/ic_screenshot_delete</item>
-    <item>@drawable/ic_settings</item>
-    <item>@drawable/ic_swap_vert</item>
-    <item>@drawable/ic_tune_black_16dp</item>
-    <item>@drawable/ic_volume_alarm_mute</item>
-    <item>@drawable/ic_volume_bt_sco</item>
-    <item>@drawable/ic_volume_media</item>
-    <item>@drawable/ic_volume_media_mute</item>
-    <item>@drawable/ic_volume_odi_captions</item>
-    <item>@drawable/ic_volume_odi_captions_disabled</item>
-    <item>@drawable/ic_volume_ringer</item>
-    <item>@drawable/ic_volume_ringer_mute</item>
-    <item>@drawable/ic_volume_ringer_vibrate</item>
-    <item>@drawable/ic_volume_voice</item>
-    <item>@drawable/stat_sys_managed_profile_status</item>
-    <item>@drawable/stat_sys_mic_none</item>
-    <item>@drawable/stat_sys_vpn_ic</item>
-  </array>
-</resources>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/IconPackOverlayTest.java b/packages/SystemUI/tests/src/com/android/systemui/IconPackOverlayTest.java
deleted file mode 100644
index ccc9afc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/IconPackOverlayTest.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import static junit.framework.Assert.fail;
-
-import static org.junit.Assert.assertEquals;
-
-import android.annotation.DrawableRes;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.text.TextUtils;
-import android.util.TypedValue;
-
-import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.XmlUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class IconPackOverlayTest extends SysuiTestCase {
-
-    private static final String[] ICON_PACK_OVERLAY_PACKAGES = {
-            "com.android.theme.icon_pack.circular.systemui",
-            "com.android.theme.icon_pack.rounded.systemui",
-            "com.android.theme.icon_pack.filled.systemui",
-    };
-
-    private static final int[] VECTOR_ATTRIBUTES = {
-            android.R.attr.tint,
-            android.R.attr.height,
-            android.R.attr.width,
-            android.R.attr.alpha,
-            android.R.attr.autoMirrored,
-    };
-
-    private final TypedValue mTargetTypedValue = new TypedValue();
-    private final TypedValue mOverlayTypedValue = new TypedValue();
-
-    private Resources mResources;
-    private TypedArray mOverlayableIcons;
-
-    @Before
-    public void setup() {
-        mResources = mContext.getResources();
-        mOverlayableIcons = mResources.obtainTypedArray(R.array.overlayable_icons);
-    }
-
-    @After
-    public void teardown() {
-        mOverlayableIcons.recycle();
-    }
-
-    /**
-     * Ensure that all icons contained in overlayable_icons_test.xml exist in all 3 overlay icon
-     * packs for systemui. This test fails if you remove or rename an overlaid icon. If so,
-     * make the same change to the corresponding drawables in {@link #ICON_PACK_OVERLAY_PACKAGES}.
-     */
-    @Test
-    public void testIconPack_containAllOverlayedIcons() {
-        StringBuilder errors = new StringBuilder();
-
-        for (String overlayPackage : ICON_PACK_OVERLAY_PACKAGES) {
-            Resources overlayResources;
-            try {
-                overlayResources = mContext.getPackageManager()
-                        .getResourcesForApplication(overlayPackage);
-            } catch (PackageManager.NameNotFoundException e) {
-                continue; // No need to test overlay resources if apk is not on the system.
-            }
-
-            for (int i = 0; i < mOverlayableIcons.length(); i++) {
-                int sysuiRid = mOverlayableIcons.getResourceId(i, 0);
-                String sysuiResourceName = mResources.getResourceName(sysuiRid);
-                String overlayResourceName = sysuiResourceName
-                        .replace(mContext.getPackageName(), overlayPackage);
-                if (overlayResources.getIdentifier(overlayResourceName, null, null)
-                        == Resources.ID_NULL) {
-                    errors.append(String.format("[%s] is not contained in overlay package [%s]",
-                            overlayResourceName, overlayPackage));
-                }
-            }
-        }
-
-        if (!TextUtils.isEmpty(errors)) {
-            fail(errors.toString());
-        }
-    }
-
-    /**
-     * Ensures that all overlay icons have the same values for {@link #VECTOR_ATTRIBUTES} as the
-     * underlying drawable in systemui. To fix this test, make the attribute change to all of the
-     * corresponding drawables in {@link #ICON_PACK_OVERLAY_PACKAGES}.
-     */
-    @Test
-    public void testIconPacks_haveEqualVectorDrawableAttributes() {
-        StringBuilder errors = new StringBuilder();
-
-        for (String overlayPackage : ICON_PACK_OVERLAY_PACKAGES) {
-            Resources overlayResources;
-            try {
-                overlayResources = mContext.getPackageManager()
-                        .getResourcesForApplication(overlayPackage);
-            } catch (PackageManager.NameNotFoundException e) {
-                continue; // No need to test overlay resources if apk is not on the system.
-            }
-
-            for (int i = 0; i < mOverlayableIcons.length(); i++) {
-                int sysuiRid = mOverlayableIcons.getResourceId(i, 0);
-                String sysuiResourceName = mResources.getResourceName(sysuiRid);
-                TypedArray sysuiAttrs = getAVDAttributes(mResources, sysuiRid);
-                if (sysuiAttrs == null) {
-                    errors.append(String.format("[%s] does not exist or is not a valid AVD.",
-                            sysuiResourceName));
-                    continue;
-                }
-
-                String overlayResourceName = sysuiResourceName
-                        .replace(mContext.getPackageName(), overlayPackage);
-                int overlayRid = overlayResources.getIdentifier(overlayResourceName, null, null);
-                TypedArray overlayAttrs = getAVDAttributes(overlayResources, overlayRid);
-                if (overlayAttrs == null) {
-                    errors.append(String.format("[%s] does not exist or is not a valid AVD.",
-                            overlayResourceName));
-                    continue;
-                }
-
-                if (!attributesEquals(sysuiAttrs, overlayAttrs)) {
-                    errors.append(String.format("[%s] AVD attributes do not match [%s]\n",
-                            sysuiResourceName, overlayResourceName));
-                }
-                sysuiAttrs.recycle();
-                overlayAttrs.recycle();
-            }
-        }
-
-        if (!TextUtils.isEmpty(errors)) {
-            fail(errors.toString());
-        }
-    }
-
-    private TypedArray getAVDAttributes(Resources resources, @DrawableRes int rid) {
-        try {
-            XmlResourceParser parser = resources.getXml(rid);
-            XmlUtils.nextElement(parser);
-            return resources.obtainAttributes(parser, VECTOR_ATTRIBUTES);
-        } catch (XmlPullParserException | IOException  | Resources.NotFoundException e) {
-            return null;
-        }
-    }
-
-    private boolean attributesEquals(TypedArray target, TypedArray overlay) {
-        assertEquals(target.length(), overlay.length());
-        for (int i = 0; i < target.length(); i++) {
-            target.getValue(i, mTargetTypedValue);
-            overlay.getValue(i, mOverlayTypedValue);
-            if (!attributesEquals(mTargetTypedValue, mOverlayTypedValue)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private boolean attributesEquals(TypedValue target, TypedValue overlay) {
-        return target.type == overlay.type && target.data == overlay.data;
-    }
-}
diff --git a/packages/overlays/tests/Android.bp b/packages/overlays/tests/Android.bp
new file mode 100644
index 0000000..343367a
--- /dev/null
+++ b/packages/overlays/tests/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2019 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.
+
+android_test {
+    name: "OverlayTests",
+
+    certificate: "platform",
+
+    srcs: ["src/**/*.java"],
+
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+
+    platform_apis: true,
+
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.espresso.core",
+        "mockito-target-minus-junit4",
+        "truth-prebuilt",
+    ],
+
+    dxflags: ["--multi-dex"],
+}
diff --git a/packages/overlays/tests/AndroidManifest.xml b/packages/overlays/tests/AndroidManifest.xml
new file mode 100644
index 0000000..6ebc555
--- /dev/null
+++ b/packages/overlays/tests/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 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.overlays">
+
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
+    <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY"/>
+    <uses-permission android:name="android.permission.SET_TIME_ZONE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.systemui"
+        android:label="Tests for Overlays">
+    </instrumentation>
+</manifest>
diff --git a/packages/overlays/tests/AndroidTest.xml b/packages/overlays/tests/AndroidTest.xml
new file mode 100644
index 0000000..8843d62
--- /dev/null
+++ b/packages/overlays/tests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 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
+  -->
+<configuration description="Runs Tests for Overlays.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="OverlayTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="OverlayTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.overlays" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/packages/overlays/tests/src/com/android/theme/icon/IconPackOverlayTest.java b/packages/overlays/tests/src/com/android/theme/icon/IconPackOverlayTest.java
new file mode 100644
index 0000000..6bc56ba
--- /dev/null
+++ b/packages/overlays/tests/src/com/android/theme/icon/IconPackOverlayTest.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2019 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.theme.icon;
+
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertEquals;
+
+import android.annotation.DrawableRes;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.text.TextUtils;
+import android.util.TypedValue;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.XmlUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class IconPackOverlayTest {
+    private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+    private static final String[] SYSTEMUI_ICON_PACK_OVERLAY_PACKAGES = {
+            "com.android.theme.icon_pack.circular.systemui",
+            "com.android.theme.icon_pack.rounded.systemui",
+            "com.android.theme.icon_pack.filled.systemui",
+    };
+    private static final String ANDROID_PACKAGE = "android";
+    private static final String[] ANDROID_ICON_PACK_OVERLAY_PACKAGES = {
+            "com.android.theme.icon_pack.circular.android",
+            "com.android.theme.icon_pack.rounded.android",
+            "com.android.theme.icon_pack.filled.android",
+    };
+    private static final String SETTINGS_PACKAGE = "com.android.settings";
+    private static final String[] SETTINGS_ICON_PACK_OVERLAY_PACKAGES = {
+            "com.android.theme.icon_pack.circular.settings",
+            "com.android.theme.icon_pack.rounded.settings",
+            "com.android.theme.icon_pack.filled.settings",
+    };
+
+    private static final int[] VECTOR_ATTRIBUTES = {
+            android.R.attr.tint,
+            android.R.attr.height,
+            android.R.attr.width,
+            android.R.attr.alpha,
+            android.R.attr.autoMirrored,
+    };
+
+    private final TypedValue mTargetTypedValue = new TypedValue();
+    private final TypedValue mOverlayTypedValue = new TypedValue();
+    private Context mContext;
+
+    @Before
+    public void setup() {
+        mContext = InstrumentationRegistry.getContext();
+    }
+
+    /**
+     * Ensure that drawable icons in icon packs targeting android have corresponding underlying
+     * drawables in android. This test fails if you remove/rename an overlaid icon in android.
+     * If so, make the same change to the corresponding drawables in the overlay packages.
+     */
+    @Test
+    public void testAndroidFramework_containsAllOverlayedIcons() {
+        containsAllOverlayedIcons(ANDROID_PACKAGE, ANDROID_ICON_PACK_OVERLAY_PACKAGES);
+    }
+
+    /**
+     * Ensure that drawable icons in icon packs targeting settings have corresponding underlying
+     * drawables in settings. This test fails if you remove/rename an overlaid icon in settings.
+     * If so, make the same change to the corresponding drawables in the overlay packages.
+     */
+    @Test
+    public void testSettings_containsAllOverlayedIcons() {
+        containsAllOverlayedIcons(SETTINGS_PACKAGE, SETTINGS_ICON_PACK_OVERLAY_PACKAGES);
+    }
+
+    /**
+     * Ensure that drawable icons in icon packs targeting systemui have corresponding underlying
+     * drawables in systemui. This test fails if you remove/rename an overlaid icon in systemui.
+     * If so, make the same change to the corresponding drawables in the overlay packages.
+     */
+    @Test
+    public void testSystemUI_containAllOverlayedIcons() {
+        containsAllOverlayedIcons(SYSTEMUI_PACKAGE, SYSTEMUI_ICON_PACK_OVERLAY_PACKAGES);
+    }
+
+    /**
+     * Ensures that all overlay icons have the same values for {@link #VECTOR_ATTRIBUTES} as the
+     * underlying drawable in android. To fix this test, make the attribute change to all of the
+     * corresponding drawables in the overlay packages.
+     */
+    @Test
+    public void testAndroidFramework_hasEqualVectorDrawableAttributes() {
+        hasEqualVectorDrawableAttributes(ANDROID_PACKAGE, ANDROID_ICON_PACK_OVERLAY_PACKAGES);
+    }
+
+    /**
+     * Ensures that all overlay icons have the same values for {@link #VECTOR_ATTRIBUTES} as the
+     * underlying drawable in settings. To fix this test, make the attribute change to all of the
+     * corresponding drawables in the overlay packages.
+     */
+    @Test
+    public void testSettings_hasEqualVectorDrawableAttributes() {
+        hasEqualVectorDrawableAttributes(SETTINGS_PACKAGE, SETTINGS_ICON_PACK_OVERLAY_PACKAGES);
+    }
+
+    /**
+     * Ensures that all overlay icons have the same values for {@link #VECTOR_ATTRIBUTES} as the
+     * underlying drawable in systemui. To fix this test, make the attribute change to all of the
+     * corresponding drawables in the overlay packages.
+     */
+    @Test
+    public void testSystemUI_hasEqualVectorDrawableAttributes() {
+        hasEqualVectorDrawableAttributes(SYSTEMUI_PACKAGE, SYSTEMUI_ICON_PACK_OVERLAY_PACKAGES);
+    }
+
+    private void containsAllOverlayedIcons(String targetPkg, String[] overlayPkgs) {
+        final Resources targetResources;
+        try {
+            targetResources = mContext.getPackageManager()
+                    .getResourcesForApplication(targetPkg);
+        } catch (PackageManager.NameNotFoundException e) {
+            return; // No need to test overlays if target package does not exist on the system.
+        }
+
+        StringBuilder errors = new StringBuilder();
+        for (String overlayPackage : overlayPkgs) {
+            final ApplicationInfo info;
+            try {
+                info = mContext.getPackageManager().getApplicationInfo(overlayPackage, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                continue; // No need to test overlay resources if apk is not on the system.
+            }
+            final List<String> iconPackDrawables = getDrawablesFromOverlay(info);
+            for (int i = 0; i < iconPackDrawables.size(); i++) {
+                String resourceName = iconPackDrawables.get(i);
+                int targetRid = targetResources.getIdentifier(resourceName, "drawable", targetPkg);
+                if (targetRid == Resources.ID_NULL) {
+                    errors.append(String.format("[%s] is not contained in the target package [%s]",
+                            resourceName, targetPkg));
+                }
+            }
+        }
+
+        if (!TextUtils.isEmpty(errors)) {
+            fail(errors.toString());
+        }
+    }
+
+    private void hasEqualVectorDrawableAttributes(String targetPkg, String[] overlayPackages) {
+        final Resources targetRes;
+        try {
+            targetRes = mContext.getPackageManager().getResourcesForApplication(targetPkg);
+        } catch (PackageManager.NameNotFoundException e) {
+            return; // No need to test overlays if target package does not exist on the system.
+        }
+
+        StringBuilder errors = new StringBuilder();
+
+        for (String overlayPkg : overlayPackages) {
+            final ApplicationInfo info;
+            try {
+                info = mContext.getPackageManager().getApplicationInfo(overlayPkg, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                continue; // No need to test overlay resources if apk is not on the system.
+            }
+            final List<String> iconPackDrawables = getDrawablesFromOverlay(info);
+            final Resources overlayRes;
+            try {
+                overlayRes = mContext.getPackageManager().getResourcesForApplication(overlayPkg);
+            } catch (PackageManager.NameNotFoundException e) {
+                continue; // No need to test overlay resources if apk is not on the system.
+            }
+
+            for (int i = 0; i < iconPackDrawables.size(); i++) {
+                String resourceName = iconPackDrawables.get(i);
+                int targetRid = targetRes.getIdentifier(resourceName, "drawable", targetPkg);
+                int overlayRid = overlayRes.getIdentifier(resourceName, "drawable", overlayPkg);
+                TypedArray targetAttrs = getAVDAttributes(targetRes, targetRid);
+                if (targetAttrs == null) {
+                    errors.append(String.format(
+                            "[%s] in pkg [%s] does not exist or is not a valid vector drawable.\n",
+                            resourceName, targetPkg));
+                    continue;
+                }
+
+                TypedArray overlayAttrs = getAVDAttributes(overlayRes, overlayRid);
+                if (overlayAttrs == null) {
+                    errors.append(String.format(
+                            "[%s] in pkg [%s] does not exist or is not a valid vector drawable.\n",
+                            resourceName, overlayPkg));
+                    continue;
+                }
+
+                if (!attributesEquals(targetAttrs, overlayAttrs)) {
+                    errors.append(String.format("[drawable/%s] in [%s] does not have the same "
+                                    + "attributes as the corresponding drawable from [%s]\n",
+                            resourceName, targetPkg, overlayPkg));
+                }
+                targetAttrs.recycle();
+                overlayAttrs.recycle();
+            }
+        }
+
+        if (!TextUtils.isEmpty(errors)) {
+            fail(errors.toString());
+        }
+    }
+
+    private TypedArray getAVDAttributes(Resources resources, @DrawableRes int rid) {
+        try {
+            XmlResourceParser parser = resources.getXml(rid);
+            XmlUtils.nextElement(parser);
+            // Always use the the test apk theme to resolve attributes.
+            return mContext.getTheme().obtainStyledAttributes(parser, VECTOR_ATTRIBUTES, 0, 0);
+        } catch (XmlPullParserException | IOException  | Resources.NotFoundException e) {
+            return null;
+        }
+    }
+
+    private boolean attributesEquals(TypedArray target, TypedArray overlay) {
+        assertEquals(target.length(), overlay.length());
+        for (int i = 0; i < target.length(); i++) {
+            target.getValue(i, mTargetTypedValue);
+            overlay.getValue(i, mOverlayTypedValue);
+            if (!attributesEquals(mTargetTypedValue, mOverlayTypedValue)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean attributesEquals(TypedValue target, TypedValue overlay) {
+        return target.type == overlay.type && target.data == overlay.data;
+    }
+
+    private static List<String> getDrawablesFromOverlay(ApplicationInfo applicationInfo) {
+        try {
+            final ArrayList<String> drawables = new ArrayList<>();
+            ZipFile file = new ZipFile(applicationInfo.sourceDir);
+            Enumeration<? extends ZipEntry> entries = file.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry element = entries.nextElement();
+                String name = element.getName();
+                if (name.contains("/drawable/")) {
+                    name = name.substring(name.lastIndexOf('/') + 1);
+                    if (name.contains(".")) {
+                        name = name.substring(0, name.indexOf('.'));
+                    }
+                    drawables.add(name);
+                }
+            }
+            return drawables;
+        } catch (IOException e) {
+            fail(String.format("Failed to retrieve drawables from package [%s] with message [%s]",
+                    applicationInfo.packageName, e.getMessage()));
+            return null;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index b36bbaa..930cf9e 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -467,7 +467,7 @@
         // the user being changed will cause a reload of all user specific settings, which causes
         // provider initialization, and propagates changes until a steady state is reached
         mCurrentUserId = UserHandle.USER_NULL;
-        onUserChangedLocked(UserHandle.USER_SYSTEM);
+        onUserChangedLocked(ActivityManager.getCurrentUser());
 
         // initialize in-memory settings values
         onBackgroundThrottleWhitelistChangedLocked();
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index bc7da3f..30a3563 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -413,15 +413,15 @@
             try {
                 synchronized (mLock) {
                     if (mNightMode != mode) {
-                        if (UserManager.get(getContext()).isPrimaryUser()) {
-                            SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME,
-                                    Integer.toString(mode));
-                        }
-
                         // Only persist setting if not in car mode
                         if (!mCarModeEnabled) {
                             Secure.putIntForUser(getContext().getContentResolver(),
                                     Secure.UI_NIGHT_MODE, mode, user);
+
+                            if (UserManager.get(getContext()).isPrimaryUser()) {
+                                SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME,
+                                        Integer.toString(mode));
+                            }
                         }
 
                         mNightMode = mode;
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index d848796..4f54e64 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1440,6 +1440,11 @@
 
     @Override
     public void onServiceChanged(AuthenticatorDescription desc, int userId, boolean removed) {
+        UserInfo user = getUserManager().getUserInfo(userId);
+        if (user == null) {
+            Log.w(TAG, "onServiceChanged: ignore removed user " + userId);
+            return;
+        }
         validateAccountsInternal(getUserAccounts(userId), false /* invalidateAuthenticatorCache */);
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8cd675c..adb0909 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -210,7 +210,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageManagerInternal.CheckPermissionDelegate;
 import android.content.pm.PackageParser;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PathPermission;
@@ -270,6 +269,7 @@
 import android.os.WorkSource;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
+import android.permission.PermissionManagerInternal.CheckPermissionDelegate;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
 import android.provider.Settings;
@@ -352,6 +352,7 @@
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.uri.GrantUri;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.utils.PriorityDump;
@@ -1561,6 +1562,7 @@
     final HiddenApiSettings mHiddenApiBlacklist;
 
     PackageManagerInternal mPackageManagerInt;
+    PermissionManagerServiceInternal mPermissionManagerInt;
 
     /**
      * Whether to force background check on all apps (for battery saver) or not.
@@ -7207,6 +7209,14 @@
         return mPackageManagerInt;
     }
 
+    private PermissionManagerServiceInternal getPermissionManagerInternalLocked() {
+        if (mPermissionManagerInt == null) {
+            mPermissionManagerInt =
+                    LocalServices.getService(PermissionManagerServiceInternal.class);
+        }
+        return mPermissionManagerInt;
+    }
+
     @Override
     public final ContentProviderHolder getContentProvider(
             IApplicationThread caller, String callingPackage, String name, int userId,
@@ -15937,7 +15947,7 @@
             // Can't call out of the system process with a lock held, so post a message.
             if (instr.mUiAutomationConnection != null) {
                 mAppOpsService.setAppOpsServiceDelegate(null);
-                getPackageManagerInternalLocked().setCheckPermissionDelegate(null);
+                getPermissionManagerInternalLocked().setCheckPermissionDelegate(null);
                 mHandler.obtainMessage(SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG,
                         instr.mUiAutomationConnection).sendToTarget();
             }
@@ -18893,7 +18903,7 @@
         synchronized (ActivityManagerService.this) {
             // If there is a delegate it should be the same instance for app ops and permissions.
             if (mAppOpsService.getAppOpsServiceDelegate()
-                    != getPackageManagerInternalLocked().getCheckPermissionDelegate()) {
+                    != getPermissionManagerInternalLocked().getCheckPermissionDelegate()) {
                 throw new IllegalStateException("Bad shell delegate state");
             }
 
@@ -18928,7 +18938,7 @@
                 final ShellDelegate shellDelegate = new ShellDelegate(
                         instr.mTargetInfo.packageName, delegateUid, permissions);
                 mAppOpsService.setAppOpsServiceDelegate(shellDelegate);
-                getPackageManagerInternalLocked().setCheckPermissionDelegate(shellDelegate);
+                getPermissionManagerInternalLocked().setCheckPermissionDelegate(shellDelegate);
                 return;
             }
         }
@@ -18942,7 +18952,7 @@
         }
         synchronized (ActivityManagerService.this) {
             mAppOpsService.setAppOpsServiceDelegate(null);
-            getPackageManagerInternalLocked().setCheckPermissionDelegate(null);
+            getPermissionManagerInternalLocked().setCheckPermissionDelegate(null);
         }
     }
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 89e97d2..6c57be8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -54,8 +54,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.hardware.hdmi.HdmiAudioSystemClient;
 import android.hardware.hdmi.HdmiControlManager;
@@ -82,11 +80,7 @@
 import android.media.IVolumeController;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.media.MediaPlayer.OnErrorListener;
 import android.media.PlayerBase;
-import android.media.SoundPool;
 import android.media.VolumePolicy;
 import android.media.audiofx.AudioEffect;
 import android.media.audiopolicy.AudioMix;
@@ -102,7 +96,6 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -137,7 +130,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -146,15 +138,11 @@
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -294,19 +282,6 @@
     // protects mRingerMode
     private final Object mSettingsLock = new Object();
 
-    private SoundPool mSoundPool;
-    private final Object mSoundEffectsLock = new Object();
-    private static final int NUM_SOUNDPOOL_CHANNELS = 4;
-
-    /* Sound effect file names  */
-    private static final String SOUND_EFFECTS_PATH = "/media/audio/ui/";
-    private static final List<String> SOUND_EFFECT_FILES = new ArrayList<String>();
-
-    /* Sound effect file name mapping sound effect id (AudioManager.FX_xxx) to
-     * file index in SOUND_EFFECT_FILES[] (first column) and indicating if effect
-     * uses soundpool (second column) */
-    private final int[][] SOUND_EFFECT_FILES_MAP = new int[AudioManager.NUM_SOUND_EFFECTS][2];
-
    /** Maximum volume index values for audio streams */
     protected static int[] MAX_STREAM_VOLUME = new int[] {
         5,  // STREAM_VOICE_CALL
@@ -453,6 +428,9 @@
      * @see System#MUTE_STREAMS_AFFECTED */
     private int mMuteAffectedStreams;
 
+    @NonNull
+    private SoundEffectsHelper mSfxHelper;
+
     /**
      * NOTE: setVibrateSetting(), getVibrateSetting(), shouldVibrate() are deprecated.
      * mVibrateSetting is just maintained during deprecation period but vibration policy is
@@ -493,14 +471,6 @@
     private boolean mSystemReady;
     // true if Intent.ACTION_USER_SWITCHED has ever been received
     private boolean mUserSwitchedReceived;
-    // listener for SoundPool sample load completion indication
-    private SoundPoolCallback mSoundPoolCallBack;
-    // thread for SoundPool listener
-    private SoundPoolListenerThread mSoundPoolListenerThread;
-    // message looper for SoundPool listener
-    private Looper mSoundPoolLooper = null;
-    // volume applied to sound played with playSoundEffect()
-    private static int sSoundEffectVolumeDb;
     // previous volume adjustment direction received by checkForRingerModeChange()
     private int mPrevVolDirection = AudioManager.ADJUST_SAME;
     // mVolumeControlStream is set by VolumePanel to temporarily force the stream type which volume
@@ -642,6 +612,8 @@
         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent");
 
+        mSfxHelper = new SoundEffectsHelper(mContext);
+
         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
         mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator();
 
@@ -732,9 +704,6 @@
                         MAX_STREAM_VOLUME[AudioSystem.STREAM_SYSTEM];
         }
 
-        sSoundEffectVolumeDb = context.getResources().getInteger(
-                com.android.internal.R.integer.config_soundEffectVolumeDb);
-
         createAudioSystemThread();
 
         AudioSystem.setErrorCallback(mAudioSystemCallback);
@@ -3369,104 +3338,30 @@
     //==========================================================================================
     // Sound Effects
     //==========================================================================================
+    private static final class LoadSoundEffectReply
+            implements SoundEffectsHelper.OnEffectsLoadCompleteHandler {
+        private static final int SOUND_EFFECTS_LOADING = 1;
+        private static final int SOUND_EFFECTS_LOADED = 0;
+        private static final int SOUND_EFFECTS_ERROR = -1;
+        private static final int SOUND_EFFECTS_LOAD_TIMEOUT_MS = 5000;
 
-    private static final String TAG_AUDIO_ASSETS = "audio_assets";
-    private static final String ATTR_VERSION = "version";
-    private static final String TAG_GROUP = "group";
-    private static final String ATTR_GROUP_NAME = "name";
-    private static final String TAG_ASSET = "asset";
-    private static final String ATTR_ASSET_ID = "id";
-    private static final String ATTR_ASSET_FILE = "file";
+        private int mStatus = SOUND_EFFECTS_LOADING;
 
-    private static final String ASSET_FILE_VERSION = "1.0";
-    private static final String GROUP_TOUCH_SOUNDS = "touch_sounds";
-
-    private static final int SOUND_EFFECTS_LOAD_TIMEOUT_MS = 5000;
-
-    class LoadSoundEffectReply {
-        public int mStatus = 1;
-    };
-
-    private void loadTouchSoundAssetDefaults() {
-        SOUND_EFFECT_FILES.add("Effect_Tick.ogg");
-        for (int i = 0; i < AudioManager.NUM_SOUND_EFFECTS; i++) {
-            SOUND_EFFECT_FILES_MAP[i][0] = 0;
-            SOUND_EFFECT_FILES_MAP[i][1] = -1;
-        }
-    }
-
-    private void loadTouchSoundAssets() {
-        XmlResourceParser parser = null;
-
-        // only load assets once.
-        if (!SOUND_EFFECT_FILES.isEmpty()) {
-            return;
+        @Override
+        public synchronized void run(boolean success) {
+            mStatus = success ? SOUND_EFFECTS_LOADED : SOUND_EFFECTS_ERROR;
+            notify();
         }
 
-        loadTouchSoundAssetDefaults();
-
-        try {
-            parser = mContext.getResources().getXml(com.android.internal.R.xml.audio_assets);
-
-            XmlUtils.beginDocument(parser, TAG_AUDIO_ASSETS);
-            String version = parser.getAttributeValue(null, ATTR_VERSION);
-            boolean inTouchSoundsGroup = false;
-
-            if (ASSET_FILE_VERSION.equals(version)) {
-                while (true) {
-                    XmlUtils.nextElement(parser);
-                    String element = parser.getName();
-                    if (element == null) {
-                        break;
-                    }
-                    if (element.equals(TAG_GROUP)) {
-                        String name = parser.getAttributeValue(null, ATTR_GROUP_NAME);
-                        if (GROUP_TOUCH_SOUNDS.equals(name)) {
-                            inTouchSoundsGroup = true;
-                            break;
-                        }
-                    }
-                }
-                while (inTouchSoundsGroup) {
-                    XmlUtils.nextElement(parser);
-                    String element = parser.getName();
-                    if (element == null) {
-                        break;
-                    }
-                    if (element.equals(TAG_ASSET)) {
-                        String id = parser.getAttributeValue(null, ATTR_ASSET_ID);
-                        String file = parser.getAttributeValue(null, ATTR_ASSET_FILE);
-                        int fx;
-
-                        try {
-                            Field field = AudioManager.class.getField(id);
-                            fx = field.getInt(null);
-                        } catch (Exception e) {
-                            Log.w(TAG, "Invalid touch sound ID: "+id);
-                            continue;
-                        }
-
-                        int i = SOUND_EFFECT_FILES.indexOf(file);
-                        if (i == -1) {
-                            i = SOUND_EFFECT_FILES.size();
-                            SOUND_EFFECT_FILES.add(file);
-                        }
-                        SOUND_EFFECT_FILES_MAP[fx][0] = i;
-                    } else {
-                        break;
-                    }
+        public synchronized boolean waitForLoaded(int attempts) {
+            while ((mStatus == SOUND_EFFECTS_LOADING) && (attempts-- > 0)) {
+                try {
+                    wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
+                } catch (InterruptedException e) {
+                    Log.w(TAG, "Interrupted while waiting sound pool loaded.");
                 }
             }
-        } catch (Resources.NotFoundException e) {
-            Log.w(TAG, "audio assets file not found", e);
-        } catch (XmlPullParserException e) {
-            Log.w(TAG, "XML parser exception reading touch sound assets", e);
-        } catch (IOException e) {
-            Log.w(TAG, "I/O exception reading touch sound assets", e);
-        } finally {
-            if (parser != null) {
-                parser.close();
-            }
+            return mStatus == SOUND_EFFECTS_LOADED;
         }
     }
 
@@ -3496,20 +3391,9 @@
      * This method must be called at first when sound effects are enabled
      */
     public boolean loadSoundEffects() {
-        int attempts = 3;
         LoadSoundEffectReply reply = new LoadSoundEffectReply();
-
-        synchronized (reply) {
-            sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, reply, 0);
-            while ((reply.mStatus == 1) && (attempts-- > 0)) {
-                try {
-                    reply.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
-                } catch (InterruptedException e) {
-                    Log.w(TAG, "loadSoundEffects Interrupted while waiting sound pool loaded.");
-                }
-            }
-        }
-        return (reply.mStatus == 0);
+        sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, reply, 0);
+        return reply.waitForLoaded(3 /*attempts*/);
     }
 
     /**
@@ -3529,61 +3413,6 @@
         sendMsg(mAudioHandler, MSG_UNLOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, null, 0);
     }
 
-    class SoundPoolListenerThread extends Thread {
-        public SoundPoolListenerThread() {
-            super("SoundPoolListenerThread");
-        }
-
-        @Override
-        public void run() {
-
-            Looper.prepare();
-            mSoundPoolLooper = Looper.myLooper();
-
-            synchronized (mSoundEffectsLock) {
-                if (mSoundPool != null) {
-                    mSoundPoolCallBack = new SoundPoolCallback();
-                    mSoundPool.setOnLoadCompleteListener(mSoundPoolCallBack);
-                }
-                mSoundEffectsLock.notify();
-            }
-            Looper.loop();
-        }
-    }
-
-    private final class SoundPoolCallback implements
-            android.media.SoundPool.OnLoadCompleteListener {
-
-        int mStatus = 1; // 1 means neither error nor last sample loaded yet
-        List<Integer> mSamples = new ArrayList<Integer>();
-
-        public int status() {
-            return mStatus;
-        }
-
-        public void setSamples(int[] samples) {
-            for (int i = 0; i < samples.length; i++) {
-                // do not wait ack for samples rejected upfront by SoundPool
-                if (samples[i] > 0) {
-                    mSamples.add(samples[i]);
-                }
-            }
-        }
-
-        public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
-            synchronized (mSoundEffectsLock) {
-                int i = mSamples.indexOf(sampleId);
-                if (i >= 0) {
-                    mSamples.remove(i);
-                }
-                if ((status != 0) || mSamples. isEmpty()) {
-                    mStatus = status;
-                    mSoundEffectsLock.notify();
-                }
-            }
-        }
-    }
-
     /** @see AudioManager#reloadAudioSettings() */
     public void reloadAudioSettings() {
         readAudioSettings(false /*userSwitch*/);
@@ -5104,230 +4933,6 @@
             Settings.Global.putInt(mContentResolver, Settings.Global.MODE_RINGER, ringerMode);
         }
 
-        private String getSoundEffectFilePath(int effectType) {
-            String filePath = Environment.getProductDirectory() + SOUND_EFFECTS_PATH
-                    + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);
-            if (!new File(filePath).isFile()) {
-                filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH
-                        + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);
-            }
-            return filePath;
-        }
-
-        private boolean onLoadSoundEffects() {
-            int status;
-
-            synchronized (mSoundEffectsLock) {
-                if (!mSystemReady) {
-                    Log.w(TAG, "onLoadSoundEffects() called before boot complete");
-                    return false;
-                }
-
-                if (mSoundPool != null) {
-                    return true;
-                }
-
-                loadTouchSoundAssets();
-
-                mSoundPool = new SoundPool.Builder()
-                        .setMaxStreams(NUM_SOUNDPOOL_CHANNELS)
-                        .setAudioAttributes(new AudioAttributes.Builder()
-                            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
-                            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                            .build())
-                        .build();
-                mSoundPoolCallBack = null;
-                mSoundPoolListenerThread = new SoundPoolListenerThread();
-                mSoundPoolListenerThread.start();
-                int attempts = 3;
-                while ((mSoundPoolCallBack == null) && (attempts-- > 0)) {
-                    try {
-                        // Wait for mSoundPoolCallBack to be set by the other thread
-                        mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
-                    } catch (InterruptedException e) {
-                        Log.w(TAG, "Interrupted while waiting sound pool listener thread.");
-                    }
-                }
-
-                if (mSoundPoolCallBack == null) {
-                    Log.w(TAG, "onLoadSoundEffects() SoundPool listener or thread creation error");
-                    if (mSoundPoolLooper != null) {
-                        mSoundPoolLooper.quit();
-                        mSoundPoolLooper = null;
-                    }
-                    mSoundPoolListenerThread = null;
-                    mSoundPool.release();
-                    mSoundPool = null;
-                    return false;
-                }
-                /*
-                 * poolId table: The value -1 in this table indicates that corresponding
-                 * file (same index in SOUND_EFFECT_FILES[] has not been loaded.
-                 * Once loaded, the value in poolId is the sample ID and the same
-                 * sample can be reused for another effect using the same file.
-                 */
-                int[] poolId = new int[SOUND_EFFECT_FILES.size()];
-                for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) {
-                    poolId[fileIdx] = -1;
-                }
-                /*
-                 * Effects whose value in SOUND_EFFECT_FILES_MAP[effect][1] is -1 must be loaded.
-                 * If load succeeds, value in SOUND_EFFECT_FILES_MAP[effect][1] is > 0:
-                 * this indicates we have a valid sample loaded for this effect.
-                 */
-
-                int numSamples = 0;
-                for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
-                    // Do not load sample if this effect uses the MediaPlayer
-                    if (SOUND_EFFECT_FILES_MAP[effect][1] == 0) {
-                        continue;
-                    }
-                    if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) {
-                        String filePath = getSoundEffectFilePath(effect);
-                        int sampleId = mSoundPool.load(filePath, 0);
-                        if (sampleId <= 0) {
-                            Log.w(TAG, "Soundpool could not load file: "+filePath);
-                        } else {
-                            SOUND_EFFECT_FILES_MAP[effect][1] = sampleId;
-                            poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = sampleId;
-                            numSamples++;
-                        }
-                    } else {
-                        SOUND_EFFECT_FILES_MAP[effect][1] =
-                                poolId[SOUND_EFFECT_FILES_MAP[effect][0]];
-                    }
-                }
-                // wait for all samples to be loaded
-                if (numSamples > 0) {
-                    mSoundPoolCallBack.setSamples(poolId);
-
-                    attempts = 3;
-                    status = 1;
-                    while ((status == 1) && (attempts-- > 0)) {
-                        try {
-                            mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
-                            status = mSoundPoolCallBack.status();
-                        } catch (InterruptedException e) {
-                            Log.w(TAG, "Interrupted while waiting sound pool callback.");
-                        }
-                    }
-                } else {
-                    status = -1;
-                }
-
-                if (mSoundPoolLooper != null) {
-                    mSoundPoolLooper.quit();
-                    mSoundPoolLooper = null;
-                }
-                mSoundPoolListenerThread = null;
-                if (status != 0) {
-                    Log.w(TAG,
-                            "onLoadSoundEffects(), Error "+status+ " while loading samples");
-                    for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
-                        if (SOUND_EFFECT_FILES_MAP[effect][1] > 0) {
-                            SOUND_EFFECT_FILES_MAP[effect][1] = -1;
-                        }
-                    }
-
-                    mSoundPool.release();
-                    mSoundPool = null;
-                }
-            }
-            return (status == 0);
-        }
-
-        /**
-         *  Unloads samples from the sound pool.
-         *  This method can be called to free some memory when
-         *  sound effects are disabled.
-         */
-        private void onUnloadSoundEffects() {
-            synchronized (mSoundEffectsLock) {
-                if (mSoundPool == null) {
-                    return;
-                }
-
-                int[] poolId = new int[SOUND_EFFECT_FILES.size()];
-                for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) {
-                    poolId[fileIdx] = 0;
-                }
-
-                for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
-                    if (SOUND_EFFECT_FILES_MAP[effect][1] <= 0) {
-                        continue;
-                    }
-                    if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == 0) {
-                        mSoundPool.unload(SOUND_EFFECT_FILES_MAP[effect][1]);
-                        SOUND_EFFECT_FILES_MAP[effect][1] = -1;
-                        poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = -1;
-                    }
-                }
-                mSoundPool.release();
-                mSoundPool = null;
-            }
-        }
-
-        private void onPlaySoundEffect(int effectType, int volume) {
-            synchronized (mSoundEffectsLock) {
-
-                onLoadSoundEffects();
-
-                if (mSoundPool == null) {
-                    return;
-                }
-                float volFloat;
-                // use default if volume is not specified by caller
-                if (volume < 0) {
-                    volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20);
-                } else {
-                    volFloat = volume / 1000.0f;
-                }
-
-                if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
-                    mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1],
-                                        volFloat, volFloat, 0, 0, 1.0f);
-                } else {
-                    MediaPlayer mediaPlayer = new MediaPlayer();
-                    try {
-                        String filePath = getSoundEffectFilePath(effectType);
-                        mediaPlayer.setDataSource(filePath);
-                        mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
-                        mediaPlayer.prepare();
-                        mediaPlayer.setVolume(volFloat);
-                        mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
-                            public void onCompletion(MediaPlayer mp) {
-                                cleanupPlayer(mp);
-                            }
-                        });
-                        mediaPlayer.setOnErrorListener(new OnErrorListener() {
-                            public boolean onError(MediaPlayer mp, int what, int extra) {
-                                cleanupPlayer(mp);
-                                return true;
-                            }
-                        });
-                        mediaPlayer.start();
-                    } catch (IOException ex) {
-                        Log.w(TAG, "MediaPlayer IOException: "+ex);
-                    } catch (IllegalArgumentException ex) {
-                        Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex);
-                    } catch (IllegalStateException ex) {
-                        Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
-                    }
-                }
-            }
-        }
-
-        private void cleanupPlayer(MediaPlayer mp) {
-            if (mp != null) {
-                try {
-                    mp.stop();
-                    mp.release();
-                } catch (IllegalStateException ex) {
-                    Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
-                }
-            }
-        }
-
         private void onPersistSafeVolumeState(int state) {
             Settings.Global.putInt(mContentResolver,
                     Settings.Global.AUDIO_SAFE_VOLUME_STATE,
@@ -5374,24 +4979,25 @@
                     break;
 
                 case MSG_UNLOAD_SOUND_EFFECTS:
-                    onUnloadSoundEffects();
+                    mSfxHelper.unloadSoundEffects();
                     break;
 
                 case MSG_LOAD_SOUND_EFFECTS:
-                    //FIXME: onLoadSoundEffects() should be executed in a separate thread as it
-                    // can take several dozens of milliseconds to complete
-                    boolean loaded = onLoadSoundEffects();
-                    if (msg.obj != null) {
-                        LoadSoundEffectReply reply = (LoadSoundEffectReply)msg.obj;
-                        synchronized (reply) {
-                            reply.mStatus = loaded ? 0 : -1;
-                            reply.notify();
+                {
+                    LoadSoundEffectReply reply = (LoadSoundEffectReply) msg.obj;
+                    if (mSystemReady) {
+                        mSfxHelper.loadSoundEffects(reply);
+                    } else {
+                        Log.w(TAG, "[schedule]loadSoundEffects() called before boot complete");
+                        if (reply != null) {
+                            reply.run(false);
                         }
                     }
+                }
                     break;
 
                 case MSG_PLAY_SOUND_EFFECT:
-                    onPlaySoundEffect(msg.arg1, msg.arg2);
+                    mSfxHelper.playSoundEffect(msg.arg1, msg.arg2);
                     break;
 
                 case MSG_SET_FORCE_USE:
@@ -6422,6 +6028,8 @@
 
         pw.println("\nAudioDeviceBroker:");
         mDeviceBroker.dump(pw, "  ");
+        pw.println("\nSoundEffects:");
+        mSfxHelper.dump(pw, "  ");
 
         pw.println("\n");
         pw.println("\nEvent logs:");
diff --git a/services/core/java/com/android/server/audio/SoundEffectsHelper.java b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
new file mode 100644
index 0000000..cf5bc8d
--- /dev/null
+++ b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2019 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.audio;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.SoundPool;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper class for managing sound effects loading / unloading
+ * used by AudioService. As its methods are called on the message handler thread
+ * of AudioService, the actual work is offloaded to a dedicated thread.
+ * This helps keeping AudioService responsive.
+ * @hide
+ */
+class SoundEffectsHelper {
+    private static final String TAG = "AS.SfxHelper";
+
+    private static final int NUM_SOUNDPOOL_CHANNELS = 4;
+
+    /* Sound effect file names  */
+    private static final String SOUND_EFFECTS_PATH = "/media/audio/ui/";
+
+    private static final int EFFECT_NOT_IN_SOUND_POOL = 0; // SoundPool sample IDs > 0
+
+    private static final int MSG_LOAD_EFFECTS = 0;
+    private static final int MSG_UNLOAD_EFFECTS = 1;
+    private static final int MSG_PLAY_EFFECT = 2;
+    private static final int MSG_LOAD_EFFECTS_TIMEOUT = 3;
+
+    interface OnEffectsLoadCompleteHandler {
+        void run(boolean success);
+    }
+
+    private final AudioEventLogger mSfxLogger = new AudioEventLogger(
+            AudioManager.NUM_SOUND_EFFECTS + 10, "Sound Effects Loading");
+
+    private final Context mContext;
+    // default attenuation applied to sound played with playSoundEffect()
+    private final int mSfxAttenuationDb;
+
+    // thread for doing all work
+    private SfxWorker mSfxWorker;
+    // thread's message handler
+    private SfxHandler mSfxHandler;
+
+    private static final class Resource {
+        final String mFileName;
+        int mSampleId;
+        boolean mLoaded;  // for effects in SoundPool
+        Resource(String fileName) {
+            mFileName = fileName;
+            mSampleId = EFFECT_NOT_IN_SOUND_POOL;
+        }
+    }
+    // All the fields below are accessed by the worker thread exclusively
+    private final List<Resource> mResources = new ArrayList<Resource>();
+    private final int[] mEffects = new int[AudioManager.NUM_SOUND_EFFECTS]; // indexes in mResources
+    private SoundPool mSoundPool;
+    private SoundPoolLoader mSoundPoolLoader;
+
+    SoundEffectsHelper(Context context) {
+        mContext = context;
+        mSfxAttenuationDb = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_soundEffectVolumeDb);
+        startWorker();
+    }
+
+    /*package*/ void loadSoundEffects(OnEffectsLoadCompleteHandler onComplete) {
+        sendMsg(MSG_LOAD_EFFECTS, 0, 0, onComplete, 0);
+    }
+
+    /**
+     *  Unloads samples from the sound pool.
+     *  This method can be called to free some memory when
+     *  sound effects are disabled.
+     */
+    /*package*/ void unloadSoundEffects() {
+        sendMsg(MSG_UNLOAD_EFFECTS, 0, 0, null, 0);
+    }
+
+    /*package*/ void playSoundEffect(int effect, int volume) {
+        sendMsg(MSG_PLAY_EFFECT, effect, volume, null, 0);
+    }
+
+    /*package*/ void dump(PrintWriter pw, String prefix) {
+        if (mSfxHandler != null) {
+            pw.println(prefix + "Message handler (watch for unhandled messages):");
+            mSfxHandler.dump(new PrintWriterPrinter(pw), "  ");
+        } else {
+            pw.println(prefix + "Message handler is null");
+        }
+        pw.println(prefix + "Default attenuation (dB): " + mSfxAttenuationDb);
+        mSfxLogger.dump(pw);
+    }
+
+    private void startWorker() {
+        mSfxWorker = new SfxWorker();
+        mSfxWorker.start();
+        synchronized (this) {
+            while (mSfxHandler == null) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    Log.w(TAG, "Interrupted while waiting " + mSfxWorker.getName() + " to start");
+                }
+            }
+        }
+    }
+
+    private void sendMsg(int msg, int arg1, int arg2, Object obj, int delayMs) {
+        mSfxHandler.sendMessageDelayed(mSfxHandler.obtainMessage(msg, arg1, arg2, obj), delayMs);
+    }
+
+    private void logEvent(String msg) {
+        mSfxLogger.log(new AudioEventLogger.StringEvent(msg));
+    }
+
+    // All the methods below run on the worker thread
+    private void onLoadSoundEffects(OnEffectsLoadCompleteHandler onComplete) {
+        if (mSoundPoolLoader != null) {
+            // Loading is ongoing.
+            mSoundPoolLoader.addHandler(onComplete);
+            return;
+        }
+        if (mSoundPool != null) {
+            if (onComplete != null) {
+                onComplete.run(true /*success*/);
+            }
+            return;
+        }
+
+        logEvent("effects loading started");
+        mSoundPool = new SoundPool.Builder()
+                .setMaxStreams(NUM_SOUNDPOOL_CHANNELS)
+                .setAudioAttributes(new AudioAttributes.Builder()
+                        .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+                        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                        .build())
+                .build();
+        loadTouchSoundAssets();
+
+        mSoundPoolLoader = new SoundPoolLoader();
+        mSoundPoolLoader.addHandler(new OnEffectsLoadCompleteHandler() {
+            @Override
+            public void run(boolean success) {
+                mSoundPoolLoader = null;
+                if (!success) {
+                    Log.w(TAG, "onLoadSoundEffects(), Error while loading samples");
+                    onUnloadSoundEffects();
+                }
+            }
+        });
+        mSoundPoolLoader.addHandler(onComplete);
+
+        int resourcesToLoad = 0;
+        for (Resource res : mResources) {
+            String filePath = getResourceFilePath(res);
+            int sampleId = mSoundPool.load(filePath, 0);
+            if (sampleId > 0) {
+                res.mSampleId = sampleId;
+                res.mLoaded = false;
+                resourcesToLoad++;
+            } else {
+                logEvent("effect " + filePath + " rejected by SoundPool");
+                Log.w(TAG, "SoundPool could not load file: " + filePath);
+            }
+        }
+
+        if (resourcesToLoad > 0) {
+            sendMsg(MSG_LOAD_EFFECTS_TIMEOUT, 0, 0, null, SOUND_EFFECTS_LOAD_TIMEOUT_MS);
+        } else {
+            logEvent("effects loading completed, no effects to load");
+            mSoundPoolLoader.onComplete(true /*success*/);
+        }
+    }
+
+    void onUnloadSoundEffects() {
+        if (mSoundPool == null) {
+            return;
+        }
+        if (mSoundPoolLoader != null) {
+            mSoundPoolLoader.addHandler(new OnEffectsLoadCompleteHandler() {
+                @Override
+                public void run(boolean success) {
+                    onUnloadSoundEffects();
+                }
+            });
+        }
+
+        logEvent("effects unloading started");
+        for (Resource res : mResources) {
+            if (res.mSampleId != EFFECT_NOT_IN_SOUND_POOL) {
+                mSoundPool.unload(res.mSampleId);
+            }
+        }
+        mSoundPool.release();
+        mSoundPool = null;
+        logEvent("effects unloading completed");
+    }
+
+    void onPlaySoundEffect(int effect, int volume) {
+        float volFloat;
+        // use default if volume is not specified by caller
+        if (volume < 0) {
+            volFloat = (float) Math.pow(10, (float) mSfxAttenuationDb / 20);
+        } else {
+            volFloat = volume / 1000.0f;
+        }
+
+        Resource res = mResources.get(mEffects[effect]);
+        if (res.mSampleId != EFFECT_NOT_IN_SOUND_POOL && res.mLoaded) {
+            mSoundPool.play(res.mSampleId, volFloat, volFloat, 0, 0, 1.0f);
+        } else {
+            MediaPlayer mediaPlayer = new MediaPlayer();
+            try {
+                String filePath = getResourceFilePath(res);
+                mediaPlayer.setDataSource(filePath);
+                mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
+                mediaPlayer.prepare();
+                mediaPlayer.setVolume(volFloat);
+                mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
+                    public void onCompletion(MediaPlayer mp) {
+                        cleanupPlayer(mp);
+                    }
+                });
+                mediaPlayer.setOnErrorListener(new OnErrorListener() {
+                    public boolean onError(MediaPlayer mp, int what, int extra) {
+                        cleanupPlayer(mp);
+                        return true;
+                    }
+                });
+                mediaPlayer.start();
+            } catch (IOException ex) {
+                Log.w(TAG, "MediaPlayer IOException: " + ex);
+            } catch (IllegalArgumentException ex) {
+                Log.w(TAG, "MediaPlayer IllegalArgumentException: " + ex);
+            } catch (IllegalStateException ex) {
+                Log.w(TAG, "MediaPlayer IllegalStateException: " + ex);
+            }
+        }
+    }
+
+    private static void cleanupPlayer(MediaPlayer mp) {
+        if (mp != null) {
+            try {
+                mp.stop();
+                mp.release();
+            } catch (IllegalStateException ex) {
+                Log.w(TAG, "MediaPlayer IllegalStateException: " + ex);
+            }
+        }
+    }
+
+    private static final String TAG_AUDIO_ASSETS = "audio_assets";
+    private static final String ATTR_VERSION = "version";
+    private static final String TAG_GROUP = "group";
+    private static final String ATTR_GROUP_NAME = "name";
+    private static final String TAG_ASSET = "asset";
+    private static final String ATTR_ASSET_ID = "id";
+    private static final String ATTR_ASSET_FILE = "file";
+
+    private static final String ASSET_FILE_VERSION = "1.0";
+    private static final String GROUP_TOUCH_SOUNDS = "touch_sounds";
+
+    private static final int SOUND_EFFECTS_LOAD_TIMEOUT_MS = 15000;
+
+    private String getResourceFilePath(Resource res) {
+        String filePath = Environment.getProductDirectory() + SOUND_EFFECTS_PATH + res.mFileName;
+        if (!new File(filePath).isFile()) {
+            filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + res.mFileName;
+        }
+        return filePath;
+    }
+
+    private void loadTouchSoundAssetDefaults() {
+        int defaultResourceIdx = mResources.size();
+        mResources.add(new Resource("Effect_Tick.ogg"));
+        for (int i = 0; i < mEffects.length; i++) {
+            mEffects[i] = defaultResourceIdx;
+        }
+    }
+
+    private void loadTouchSoundAssets() {
+        XmlResourceParser parser = null;
+
+        // only load assets once.
+        if (!mResources.isEmpty()) {
+            return;
+        }
+
+        loadTouchSoundAssetDefaults();
+
+        try {
+            parser = mContext.getResources().getXml(com.android.internal.R.xml.audio_assets);
+
+            XmlUtils.beginDocument(parser, TAG_AUDIO_ASSETS);
+            String version = parser.getAttributeValue(null, ATTR_VERSION);
+            boolean inTouchSoundsGroup = false;
+
+            if (ASSET_FILE_VERSION.equals(version)) {
+                while (true) {
+                    XmlUtils.nextElement(parser);
+                    String element = parser.getName();
+                    if (element == null) {
+                        break;
+                    }
+                    if (element.equals(TAG_GROUP)) {
+                        String name = parser.getAttributeValue(null, ATTR_GROUP_NAME);
+                        if (GROUP_TOUCH_SOUNDS.equals(name)) {
+                            inTouchSoundsGroup = true;
+                            break;
+                        }
+                    }
+                }
+                while (inTouchSoundsGroup) {
+                    XmlUtils.nextElement(parser);
+                    String element = parser.getName();
+                    if (element == null) {
+                        break;
+                    }
+                    if (element.equals(TAG_ASSET)) {
+                        String id = parser.getAttributeValue(null, ATTR_ASSET_ID);
+                        String file = parser.getAttributeValue(null, ATTR_ASSET_FILE);
+                        int fx;
+
+                        try {
+                            Field field = AudioManager.class.getField(id);
+                            fx = field.getInt(null);
+                        } catch (Exception e) {
+                            Log.w(TAG, "Invalid touch sound ID: " + id);
+                            continue;
+                        }
+
+                        mEffects[fx] = findOrAddResourceByFileName(file);
+                    } else {
+                        break;
+                    }
+                }
+            }
+        } catch (Resources.NotFoundException e) {
+            Log.w(TAG, "audio assets file not found", e);
+        } catch (XmlPullParserException e) {
+            Log.w(TAG, "XML parser exception reading touch sound assets", e);
+        } catch (IOException e) {
+            Log.w(TAG, "I/O exception reading touch sound assets", e);
+        } finally {
+            if (parser != null) {
+                parser.close();
+            }
+        }
+    }
+
+    private int findOrAddResourceByFileName(String fileName) {
+        for (int i = 0; i < mResources.size(); i++) {
+            if (mResources.get(i).mFileName.equals(fileName)) {
+                return i;
+            }
+        }
+        int result = mResources.size();
+        mResources.add(new Resource(fileName));
+        return result;
+    }
+
+    private Resource findResourceBySampleId(int sampleId) {
+        for (Resource res : mResources) {
+            if (res.mSampleId == sampleId) {
+                return res;
+            }
+        }
+        return null;
+    }
+
+    private class SfxWorker extends Thread {
+        SfxWorker() {
+            super("AS.SfxWorker");
+        }
+
+        @Override
+        public void run() {
+            Looper.prepare();
+            synchronized (SoundEffectsHelper.this) {
+                mSfxHandler = new SfxHandler();
+                SoundEffectsHelper.this.notify();
+            }
+            Looper.loop();
+        }
+    }
+
+    private class SfxHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_LOAD_EFFECTS:
+                    onLoadSoundEffects((OnEffectsLoadCompleteHandler) msg.obj);
+                    break;
+                case MSG_UNLOAD_EFFECTS:
+                    onUnloadSoundEffects();
+                    break;
+                case MSG_PLAY_EFFECT:
+                    onLoadSoundEffects(new OnEffectsLoadCompleteHandler() {
+                        @Override
+                        public void run(boolean success) {
+                            if (success) {
+                                onPlaySoundEffect(msg.arg1 /*effect*/, msg.arg2 /*volume*/);
+                            }
+                        }
+                    });
+                    break;
+                case MSG_LOAD_EFFECTS_TIMEOUT:
+                    if (mSoundPoolLoader != null) {
+                        mSoundPoolLoader.onTimeout();
+                    }
+                    break;
+            }
+        }
+    }
+
+    private class SoundPoolLoader implements
+            android.media.SoundPool.OnLoadCompleteListener {
+
+        private List<OnEffectsLoadCompleteHandler> mLoadCompleteHandlers =
+                new ArrayList<OnEffectsLoadCompleteHandler>();
+
+        SoundPoolLoader() {
+            // SoundPool use the current Looper when creating its message handler.
+            // Since SoundPoolLoader is created on the SfxWorker thread, SoundPool's
+            // message handler ends up running on it (it's OK to have multiple
+            // handlers on the same Looper). Thus, onLoadComplete gets executed
+            // on the worker thread.
+            mSoundPool.setOnLoadCompleteListener(this);
+        }
+
+        void addHandler(OnEffectsLoadCompleteHandler handler) {
+            if (handler != null) {
+                mLoadCompleteHandlers.add(handler);
+            }
+        }
+
+        @Override
+        public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
+            if (status == 0) {
+                int remainingToLoad = 0;
+                for (Resource res : mResources) {
+                    if (res.mSampleId == sampleId && !res.mLoaded) {
+                        logEvent("effect " + res.mFileName + " loaded");
+                        res.mLoaded = true;
+                    }
+                    if (res.mSampleId != EFFECT_NOT_IN_SOUND_POOL && !res.mLoaded) {
+                        remainingToLoad++;
+                    }
+                }
+                if (remainingToLoad == 0) {
+                    onComplete(true);
+                }
+            } else {
+                Resource res = findResourceBySampleId(sampleId);
+                String filePath;
+                if (res != null) {
+                    filePath = getResourceFilePath(res);
+                } else {
+                    filePath = "with unknown sample ID " + sampleId;
+                }
+                logEvent("effect " + filePath + " loading failed, status " + status);
+                Log.w(TAG, "onLoadSoundEffects(), Error " + status + " while loading sample "
+                        + filePath);
+                onComplete(false);
+            }
+        }
+
+        void onTimeout() {
+            onComplete(false);
+        }
+
+        void onComplete(boolean success) {
+            mSoundPool.setOnLoadCompleteListener(null);
+            for (OnEffectsLoadCompleteHandler handler : mLoadCompleteHandlers) {
+                handler.run(success);
+            }
+            logEvent("effects loading " + (success ? "completed" : "failed"));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 0c4f0bd..f08423e 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -497,9 +497,9 @@
         }
     }
 
-    private final class BiometricTaskStackListener extends TaskStackListener {
+    private final Runnable mOnTaskStackChangedRunnable = new Runnable() {
         @Override
-        public void onTaskStackChanged() {
+        public void run() {
             try {
                 if (!(mCurrentClient instanceof AuthenticationClient)) {
                     return;
@@ -514,8 +514,8 @@
                     final String topPackage = runningTasks.get(0).topActivity.getPackageName();
                     if (!topPackage.contentEquals(currentClient)
                             && !mCurrentClient.isAlreadyDone()) {
-                        Slog.e(getTag(), "Stopping background authentication, top: " + topPackage
-                                + " currentClient: " + currentClient);
+                        Slog.e(getTag(), "Stopping background authentication, top: "
+                                + topPackage + " currentClient: " + currentClient);
                         mCurrentClient.stop(false /* initiatedByClient */);
                     }
                 }
@@ -523,6 +523,13 @@
                 Slog.e(getTag(), "Unable to get running tasks", e);
             }
         }
+    };
+
+    private final class BiometricTaskStackListener extends TaskStackListener {
+        @Override
+        public void onTaskStackChanged() {
+            mHandler.post(mOnTaskStackChangedRunnable);
+        }
     }
 
     private final class ResetClientStateRunnable implements Runnable {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 6c34e13..d3346cd 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -232,7 +232,6 @@
 import com.android.server.SystemConfig;
 
 import libcore.io.IoUtils;
-import libcore.util.EmptyArray;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
@@ -540,7 +539,7 @@
     private final SparseArray<String> mSubIdToSubscriberId = new SparseArray<>();
     /** Set of all merged subscriberId as of last update */
     @GuardedBy("mNetworkPoliciesSecondLock")
-    private String[] mMergedSubscriberIds = EmptyArray.STRING;
+    private List<String[]> mMergedSubscriberIds = new ArrayList<>();
 
     /**
      * Indicates the uids restricted by admin from accessing metered data. It's a mapping from
@@ -1801,7 +1800,7 @@
         final SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
 
         final int[] subIds = ArrayUtils.defeatNullable(sm.getActiveSubscriptionIdList());
-        final String[] mergedSubscriberIds = ArrayUtils.defeatNullable(tm.getMergedSubscriberIds());
+        final List<String[]> mergedSubscriberIdsList = new ArrayList();
 
         final SparseArray<String> subIdToSubscriberId = new SparseArray<>(subIds.length);
         for (int subId : subIds) {
@@ -1811,6 +1810,10 @@
             } else {
                 Slog.wtf(TAG, "Missing subscriberId for subId " + subId);
             }
+
+            String[] mergedSubscriberId = ArrayUtils.defeatNullable(
+                    tm.createForSubscriptionId(subId).getMergedSubscriberIdsFromGroup());
+            mergedSubscriberIdsList.add(mergedSubscriberId);
         }
 
         synchronized (mNetworkPoliciesSecondLock) {
@@ -1820,7 +1823,7 @@
                         subIdToSubscriberId.valueAt(i));
             }
 
-            mMergedSubscriberIds = mergedSubscriberIds;
+            mMergedSubscriberIds = mergedSubscriberIdsList;
         }
 
         Trace.traceEnd(TRACE_TAG_NETWORK);
@@ -3373,8 +3376,10 @@
                 fout.decreaseIndent();
 
                 fout.println();
-                fout.println("Merged subscriptions: "
-                        + Arrays.toString(NetworkIdentity.scrubSubscriberId(mMergedSubscriberIds)));
+                for (String[] mergedSubscribers : mMergedSubscriberIds) {
+                    fout.println("Merged subscriptions: " + Arrays.toString(
+                            NetworkIdentity.scrubSubscriberId(mergedSubscribers)));
+                }
 
                 fout.println();
                 fout.println("Policy for UIDs:");
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5ab8ec3..8660d19 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -37,11 +37,11 @@
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER;
 import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
@@ -90,6 +90,7 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
+import static android.permission.PermissionManager.KILL_APP_REASON_GIDS_CHANGED;
 
 import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
 import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
@@ -144,7 +145,6 @@
 import android.content.pm.FallbackCategoryProvider;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IDexModuleRegisterCallback;
-import android.content.pm.IOnPermissionsChangeListener;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageDeleteObserver2;
@@ -168,9 +168,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
 import android.content.pm.PackageManager.ModuleInfoFlags;
-import android.content.pm.PackageManager.PermissionWhitelistFlags;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageManagerInternal.CheckPermissionDelegate;
 import android.content.pm.PackageManagerInternal.PackageListObserver;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ActivityIntentInfo;
@@ -291,7 +289,6 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.IntPair;
 import com.android.internal.util.Preconditions;
 import com.android.server.AttributeCache;
 import com.android.server.DeviceIdleController;
@@ -317,7 +314,6 @@
 import com.android.server.pm.permission.DefaultPermissionGrantPolicy;
 import com.android.server.pm.permission.PermissionManagerService;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
-import com.android.server.pm.permission.PermissionManagerServiceInternal.PermissionCallback;
 import com.android.server.pm.permission.PermissionsState;
 import com.android.server.security.VerityUtils;
 import com.android.server.storage.DeviceStorageMonitorInternal;
@@ -580,12 +576,6 @@
 
     public static final String PLATFORM_PACKAGE_NAME = "android";
 
-    private static final String KILL_APP_REASON_GIDS_CHANGED =
-            "permission grant or revoke changed gids";
-
-    private static final String KILL_APP_REASON_PERMISSIONS_REVOKED =
-            "permissions revoked";
-
     private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
 
     private static final String PACKAGE_SCHEME = "package";
@@ -915,8 +905,6 @@
     private AtomicInteger mNextMoveId = new AtomicInteger();
     private final MoveCallbacks mMoveCallbacks;
 
-    private final OnPermissionChangeListeners mOnPermissionChangeListeners;
-
     // Cache of users who need badging.
     private final SparseBooleanArray mUserNeedsBadging = new SparseBooleanArray();
 
@@ -1005,9 +993,6 @@
     }
 
     @GuardedBy("mPackages")
-    private CheckPermissionDelegate mCheckPermissionDelegate;
-
-    @GuardedBy("mPackages")
     private PackageManagerInternal.DefaultBrowserProvider mDefaultBrowserProvider;
 
     @GuardedBy("mPackages")
@@ -1766,86 +1751,6 @@
         }
     }
 
-    private PermissionCallback mPermissionCallback = new PermissionCallback() {
-        @Override
-        public void onGidsChanged(int appId, @UserIdInt int userId) {
-            mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED));
-        }
-
-        @Override
-        public void onPermissionGranted(int uid, @UserIdInt int userId) {
-            mOnPermissionChangeListeners.onPermissionsChanged(uid);
-
-            // Not critical; if this is lost, the application has to request again.
-            synchronized (mPackages) {
-                mSettings.writeRuntimePermissionsForUserLPr(userId, false);
-            }
-        }
-
-        @Override
-        public void onInstallPermissionGranted() {
-            synchronized (mPackages) {
-                scheduleWriteSettingsLocked();
-            }
-        }
-
-        @Override
-        public void onPermissionRevoked(int uid, @UserIdInt int userId) {
-            mOnPermissionChangeListeners.onPermissionsChanged(uid);
-
-            synchronized (mPackages) {
-                // Critical; after this call the application should never have the permission
-                mSettings.writeRuntimePermissionsForUserLPr(userId, true);
-            }
-
-            final int appId = UserHandle.getAppId(uid);
-            killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
-        }
-
-        @Override
-        public void onInstallPermissionRevoked() {
-            synchronized (mPackages) {
-                scheduleWriteSettingsLocked();
-            }
-        }
-
-        @Override
-        public void onPermissionUpdated(@UserIdInt int[] updatedUserIds, boolean sync) {
-            synchronized (mPackages) {
-                for (int userId : updatedUserIds) {
-                    mSettings.writeRuntimePermissionsForUserLPr(userId, sync);
-                }
-            }
-        }
-
-        @Override
-        public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
-                int uid) {
-            onPermissionUpdated(updatedUserIds, sync);
-            mOnPermissionChangeListeners.onPermissionsChanged(uid);
-        }
-
-        @Override
-        public void onInstallPermissionUpdated() {
-            synchronized (mPackages) {
-                scheduleWriteSettingsLocked();
-            }
-        }
-
-        @Override
-        public void onInstallPermissionUpdatedNotifyListener(int uid) {
-            onInstallPermissionUpdated();
-            mOnPermissionChangeListeners.onPermissionsChanged(uid);
-        }
-
-        @Override
-        public void onPermissionRemoved() {
-            synchronized (mPackages) {
-                mSettings.writeLPr();
-            }
-        }
-    };
-
     private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions,
             boolean killApp, boolean virtualPreload,
             String[] grantedPermissions, List<String> whitelistedRestrictedPermissions,
@@ -1866,8 +1771,7 @@
                     && !whitelistedRestrictedPermissions.isEmpty()) {
                 mPermissionManager.setWhitelistedRestrictedPermissions(
                         res.pkg, res.newUsers, whitelistedRestrictedPermissions,
-                        Process.myUid(), PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER,
-                        mPermissionCallback);
+                        Process.myUid(), FLAG_PERMISSION_WHITELIST_INSTALLER);
             }
 
             // Now that we successfully installed the package, grant runtime
@@ -1878,8 +1782,7 @@
             if (grantPermissions) {
                 final int callingUid = Binder.getCallingUid();
                 mPermissionManager.grantRequestedRuntimePermissions(
-                        res.pkg, res.newUsers, grantedPermissions, callingUid,
-                        mPermissionCallback);
+                        res.pkg, res.newUsers, grantedPermissions, callingUid);
             }
 
             final String installerPackageName =
@@ -1895,7 +1798,7 @@
             if (res.pkg.parentPackage != null) {
                 final int callingUid = Binder.getCallingUid();
                 mPermissionManager.grantRuntimePermissionsGrantedToDisabledPackage(
-                        res.pkg, callingUid, mPermissionCallback);
+                        res.pkg, callingUid);
             }
 
             synchronized (mPackages) {
@@ -2497,9 +2400,6 @@
 
         mViewCompiler = new ViewCompiler(mInstallLock, mInstaller);
 
-        mOnPermissionChangeListeners = new OnPermissionChangeListeners(
-                FgThread.get().getLooper());
-
         getDefaultDisplayMetrics(context, mMetrics);
 
         t.traceBegin("get system config");
@@ -3200,8 +3100,7 @@
                         + mSdkVersion + "; regranting permissions for internal storage");
             }
             mPermissionManager.updateAllPermissions(
-                    StorageManager.UUID_PRIVATE_INTERNAL, sdkUpdated, mPackages.values(),
-                    mPermissionCallback);
+                    StorageManager.UUID_PRIVATE_INTERNAL, sdkUpdated);
             ver.sdkVersion = mSdkVersion;
 
             // If this is the first boot or an update from pre-M, and it is a normal
@@ -3497,8 +3396,7 @@
                     } catch (PackageManagerException e) {
                         Slog.e(TAG, "updateAllSharedLibrariesLPw failed: ", e);
                     }
-                    mPermissionManager.updatePermissions(pkg.packageName, pkg, mPackages.values(),
-                            mPermissionCallback);
+                    mPermissionManager.updatePermissions(pkg.packageName, pkg);
                     mSettings.writeLPr();
                 }
             } catch (PackageManagerException e) {
@@ -4569,55 +4467,6 @@
         return -1;
     }
 
-    /**
-     * Check if any package sharing/holding a uid has a low enough target SDK.
-     *
-     * @param uid The uid of the packages
-     * @param higherTargetSDK The target SDK that might be higher than the searched package
-     *
-     * @return {@code true} if there is a package sharing/holding the uid with
-     * {@code package.targetSDK < higherTargetSDK}
-     */
-    private boolean hasTargetSdkInUidLowerThan(int uid, int higherTargetSDK) {
-        int userId = UserHandle.getUserId(uid);
-
-        synchronized (mPackages) {
-            Object obj = mSettings.getSettingLPr(UserHandle.getAppId(uid));
-            if (obj == null) {
-                return false;
-            }
-
-            if (obj instanceof PackageSetting) {
-                final PackageSetting ps = (PackageSetting) obj;
-
-                if (!ps.getInstalled(userId)) {
-                    return false;
-                }
-
-                return ps.pkg.applicationInfo.targetSdkVersion < higherTargetSDK;
-            } else if (obj instanceof SharedUserSetting) {
-                final SharedUserSetting sus = (SharedUserSetting) obj;
-
-                final int numPkgs = sus.packages.size();
-                for (int i = 0; i < numPkgs; i++) {
-                    final PackageSetting ps = sus.packages.valueAt(i);
-
-                    if (!ps.getInstalled(userId)) {
-                        continue;
-                    }
-
-                    if (ps.pkg.applicationInfo.targetSdkVersion < higherTargetSDK) {
-                        return true;
-                    }
-                }
-
-                return false;
-            } else {
-                return false;
-            }
-        }
-    }
-
     @Override
     public int[] getPackageGids(String packageName, int flags, int userId) {
         if (!sUserManager.exists(userId)) return null;
@@ -5627,46 +5476,26 @@
         }
     }
 
+    // NOTE: Can't remove due to unsupported app usage
     @Override
     public int checkPermission(String permName, String pkgName, int userId) {
-        final CheckPermissionDelegate checkPermissionDelegate;
-        synchronized (mPackages) {
-            if (mCheckPermissionDelegate == null)  {
-                return checkPermissionImpl(permName, pkgName, userId);
-            }
-            checkPermissionDelegate = mCheckPermissionDelegate;
-        }
-        return checkPermissionDelegate.checkPermission(permName, pkgName, userId,
-                PackageManagerService.this::checkPermissionImpl);
+        try {
+            // Because this is accessed via the package manager service AIDL,
+            // go through the permission manager service AIDL
+            return mPermissionManagerService.checkPermission(permName, pkgName, userId);
+        } catch (RemoteException ignore) { }
+        return PackageManager.PERMISSION_DENIED;
     }
 
-    private int checkPermissionImpl(String permName, String pkgName, int userId) {
-        return mPermissionManager.checkPermission(permName, pkgName, getCallingUid(), userId);
-    }
-
+    // NOTE: Can't remove without a major refactor. Keep around for now.
     @Override
     public int checkUidPermission(String permName, int uid) {
-        final CheckPermissionDelegate checkPermissionDelegate;
-        synchronized (mPackages) {
-            if (mCheckPermissionDelegate == null)  {
-                return checkUidPermissionImpl(permName, uid);
-            }
-            checkPermissionDelegate = mCheckPermissionDelegate;
-        }
-        return checkPermissionDelegate.checkUidPermission(permName, uid,
-                PackageManagerService.this::checkUidPermissionImpl);
-    }
-
-    private int checkUidPermissionImpl(String permName, int uid) {
-        synchronized (mPackages) {
-            final String[] packageNames = getPackagesForUid(uid);
-            PackageParser.Package pkg = null;
-            final int N = packageNames == null ? 0 : packageNames.length;
-            for (int i = 0; pkg == null && i < N; i++) {
-                pkg = mPackages.get(packageNames[i]);
-            }
-            return mPermissionManager.checkUidPermission(permName, pkg, uid, getCallingUid());
-        }
+        try {
+            // Because this is accessed via the package manager service AIDL,
+            // go through the permission manager service AIDL
+            return mPermissionManagerService.checkUidPermission(permName, uid);
+        } catch (RemoteException ignore) { }
+        return PackageManager.PERMISSION_DENIED;
     }
 
     @Override
@@ -5695,7 +5524,8 @@
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            final int flags = getPermissionFlags(permission, packageName, userId);
+            final int flags = mPermissionManager
+                    .getPermissionFlags(permission, packageName, Binder.getCallingUid(), userId);
             return (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -5747,338 +5577,18 @@
         } catch (RemoteException ignore) { }
     }
 
+    // NOTE: Can't remove due to unsupported app usage
     @Override
     public void grantRuntimePermission(String packageName, String permName, final int userId) {
-        boolean overridePolicy = (checkUidPermission(
-                Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, Binder.getCallingUid())
-                == PackageManager.PERMISSION_GRANTED);
-
-        mPermissionManager.grantRuntimePermission(permName, packageName, overridePolicy,
-                getCallingUid(), userId, mPermissionCallback);
-    }
-
-    @Override
-    public void revokeRuntimePermission(String packageName, String permName, int userId) {
-        boolean overridePolicy = (checkUidPermission(
-                Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, Binder.getCallingUid())
-                == PackageManager.PERMISSION_GRANTED);
-
-        mPermissionManager.revokeRuntimePermission(permName, packageName, overridePolicy,
-                userId, mPermissionCallback);
-    }
-
-    @Override
-    public void resetRuntimePermissions() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
-                "revokeRuntimePermission");
-
-        int callingUid = Binder.getCallingUid();
-        if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                    "resetRuntimePermissions");
-        }
-
-        synchronized (mPackages) {
-            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++) {
-                    PackageParser.Package pkg = mPackages.valueAt(i);
-                    if (!(pkg.mExtras instanceof PackageSetting)) {
-                        continue;
-                    }
-                    PackageSetting ps = (PackageSetting) pkg.mExtras;
-                    resetUserChangesToRuntimePermissionsAndFlagsLPw(ps, userId);
-                }
-            }
-        }
-    }
-
-    @Override
-    public int getPermissionFlags(String permName, String packageName, int userId) {
-        return mPermissionManager.getPermissionFlags(
-                permName, packageName, getCallingUid(), userId);
-    }
-
-    @Override
-    public void updatePermissionFlags(String permName, String packageName, int flagMask,
-            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
-        int callingUid = getCallingUid();
-        boolean overridePolicy = false;
-
-        if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) {
-            long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0) {
-                    if (checkAdjustPolicyFlagPermission) {
-                        mContext.enforceCallingOrSelfPermission(
-                                Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
-                                "Need " + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY
-                                        + " to change policy flags");
-                    } else if (!hasTargetSdkInUidLowerThan(callingUid, Build.VERSION_CODES.Q)) {
-                        throw new IllegalArgumentException(
-                                Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY + " needs "
-                                        + " to be checked for packages targeting "
-                                        + Build.VERSION_CODES.Q + " or later when changing policy "
-                                        + "flags");
-                    }
-
-                    overridePolicy = true;
-                }
-            } finally {
-                Binder.restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        mPermissionManager.updatePermissionFlags(
-                permName, packageName, flagMask, flagValues, callingUid, userId,
-                overridePolicy, mPermissionCallback);
-    }
-
-    /**
-     * Update the permission flags for all packages and runtime permissions of a user in order
-     * to allow device or profile owner to remove POLICY_FIXED.
-     */
-    @Override
-    public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId) {
-        synchronized (mPackages) {
-            final boolean changed = mPermissionManager.updatePermissionFlagsForAllApps(
-                    flagMask, flagValues, getCallingUid(), userId, mPackages.values(),
-                    mPermissionCallback);
-            if (changed) {
-                mSettings.writeRuntimePermissionsForUserLPr(userId, false);
-            }
-        }
-    }
-
-    @Override
-    public @Nullable List<String> getWhitelistedRestrictedPermissions(@NonNull String packageName,
-            @PermissionWhitelistFlags int whitelistFlags, @UserIdInt int userId) {
-        Preconditions.checkNotNull(packageName);
-        Preconditions.checkFlagsArgument(whitelistFlags,
-                PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
-                        | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
-                        | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
-        Preconditions.checkArgumentNonNegative(userId, null);
-
-        if (UserHandle.getCallingUserId() != userId) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS,
-                    "getWhitelistedRestrictedPermissions for user " + userId);
-        }
-
-        final PackageParser.Package pkg;
-
-        synchronized (mPackages) {
-            final PackageSetting packageSetting = mSettings.mPackages.get(packageName);
-            if (packageSetting == null) {
-                Slog.w(TAG, "Unknown package: " + packageName);
-                return null;
-            }
-
-            pkg = packageSetting.pkg;
-
-            final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission(
-                    Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
-                            == PackageManager.PERMISSION_GRANTED;
-            final PackageSetting installerPackageSetting = mSettings.mPackages.get(
-                    packageSetting.installerPackageName);
-            final boolean isCallerInstallerOnRecord = installerPackageSetting != null
-                    && UserHandle.isSameApp(installerPackageSetting.appId, Binder.getCallingUid());
-
-            if ((whitelistFlags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0
-                    && !isCallerPrivileged) {
-                throw new SecurityException("Querying system whitelist requires "
-                        + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
-            }
-
-            if ((whitelistFlags & (PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
-                    | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) != 0) {
-                if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
-                    throw new SecurityException("Querying upgrade or installer whitelist"
-                            + " requires being installer on record or "
-                            + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
-                }
-            }
-
-            if (filterAppAccessLPr(packageSetting, Binder.getCallingUid(),
-                    UserHandle.getCallingUserId())) {
-                return null;
-            }
-        }
-
-        final long identity = Binder.clearCallingIdentity();
         try {
-            return mPermissionManager.getWhitelistedRestrictedPermissions(
-                    pkg, whitelistFlags, userId);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+            // Because this is accessed via the package manager service AIDL,
+            // go through the permission manager service AIDL
+            mPermissionManagerService.grantRuntimePermission(packageName, permName, userId);
+        } catch (RemoteException ignore) { }
     }
 
     @Override
-    public boolean addWhitelistedRestrictedPermission(@NonNull String packageName,
-            @NonNull String permission, @PermissionWhitelistFlags int whitelistFlags,
-            @UserIdInt int userId) {
-        // Other argument checks are done in get/setWhitelistedRestrictedPermissions
-        Preconditions.checkNotNull(permission);
-
-        if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permission)) {
-            return false;
-        }
-
-        List<String> permissions = getWhitelistedRestrictedPermissions(packageName,
-                whitelistFlags, userId);
-        if (permissions == null) {
-            permissions = new ArrayList<>(1);
-        }
-        if (permissions.indexOf(permission) < 0) {
-            permissions.add(permission);
-            return setWhitelistedRestrictedPermissions(packageName, permissions,
-                    whitelistFlags, userId);
-        }
-        return false;
-    }
-
-    private boolean checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(
-            @NonNull String permission) {
-        synchronized (mPackages) {
-            final BasePermission bp = mPermissionManager.getPermissionTEMP(permission);
-            if (bp == null) {
-                Slog.w(TAG, "No such permissions: " + permission);
-                return false;
-            }
-            if (bp.isHardOrSoftRestricted() && bp.isImmutablyRestricted()
-                    && mContext.checkCallingOrSelfPermission(
-                    Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Cannot modify whitelisting of an immutably "
-                        + "restricted permission: " + permission);
-            }
-            return true;
-        }
-    }
-
-    @Override
-    public boolean removeWhitelistedRestrictedPermission(@NonNull String packageName,
-            @NonNull String permission, @PermissionWhitelistFlags int whitelistFlags,
-            @UserIdInt int userId) {
-        // Other argument checks are done in get/setWhitelistedRestrictedPermissions
-        Preconditions.checkNotNull(permission);
-
-        if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permission)) {
-            return false;
-        }
-
-        final List<String> permissions = getWhitelistedRestrictedPermissions(packageName,
-                whitelistFlags, userId);
-        if (permissions != null && permissions.remove(permission)) {
-            return setWhitelistedRestrictedPermissions(packageName, permissions,
-                    whitelistFlags, userId);
-        }
-        return false;
-    }
-
-    private boolean setWhitelistedRestrictedPermissions(@NonNull String packageName,
-            @Nullable List<String> permissions, @PermissionWhitelistFlags int whitelistFlag,
-            @UserIdInt int userId) {
-        Preconditions.checkNotNull(packageName);
-        Preconditions.checkFlagsArgument(whitelistFlag,
-                PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
-                        | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
-                        | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
-        Preconditions.checkArgument(Integer.bitCount(whitelistFlag) == 1);
-        Preconditions.checkArgumentNonNegative(userId, null);
-
-        if (UserHandle.getCallingUserId() != userId) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.INTERACT_ACROSS_USERS,
-                    "setWhitelistedRestrictedPermissions for user " + userId);
-        }
-
-        final PackageParser.Package pkg;
-
-        synchronized (mPackages) {
-            final PackageSetting packageSetting = mSettings.mPackages.get(packageName);
-            if (packageSetting == null) {
-                Slog.w(TAG, "Unknown package: " + packageName);
-                return false;
-            }
-
-            pkg = packageSetting.pkg;
-
-            final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission(
-                    Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
-                            == PackageManager.PERMISSION_GRANTED;
-            final PackageSetting installerPackageSetting = mSettings.mPackages.get(
-                    packageSetting.installerPackageName);
-            final boolean isCallerInstallerOnRecord = installerPackageSetting != null
-                    && UserHandle.isSameApp(installerPackageSetting.appId, Binder.getCallingUid());
-
-            if ((whitelistFlag & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0
-                    && !isCallerPrivileged) {
-                throw new SecurityException("Modifying system whitelist requires "
-                        + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
-            }
-
-            if ((whitelistFlag & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) {
-                if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
-                    throw new SecurityException("Modifying upgrade whitelist requires"
-                            + " being installer on record or "
-                            + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
-                }
-                final List<String> whitelistedPermissions = getWhitelistedRestrictedPermissions(
-                        packageName, whitelistFlag, userId);
-                if (permissions == null || permissions.isEmpty()) {
-                    if (whitelistedPermissions == null || whitelistedPermissions.isEmpty()) {
-                        return true;
-                    }
-                } else {
-                    // Only the system can add and remove while the installer can only remove.
-                    final int permissionCount = permissions.size();
-                    for (int i = 0; i < permissionCount; i++) {
-                        if ((whitelistedPermissions == null
-                                || !whitelistedPermissions.contains(permissions.get(i)))
-                                && !isCallerPrivileged) {
-                            throw new SecurityException("Adding to upgrade whitelist requires"
-                                    + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
-                        }
-                    }
-                }
-            }
-
-            if ((whitelistFlag & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
-                if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
-                    throw new SecurityException("Modifying installer whitelist requires"
-                            + " being installer on record or "
-                            + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
-                }
-            }
-
-            if (filterAppAccessLPr(packageSetting, Binder.getCallingUid(),
-                    UserHandle.getCallingUserId())) {
-                return false;
-            }
-        }
-
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            mPermissionManager.setWhitelistedRestrictedPermissions(pkg,
-                    new int[]{userId}, permissions, Process.myUid(), whitelistFlag,
-                    mPermissionCallback);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-
-        return true;
-    }
-
-    @Override
-    public boolean shouldShowRequestPermissionRationale(String permissionName,
+    public boolean shouldShowRequestPermissionRationale(String permName,
             String packageName, int userId) {
         if (UserHandle.getCallingUserId() != userId) {
             mContext.enforceCallingPermission(
@@ -6091,7 +5601,7 @@
             return false;
         }
 
-        if (checkPermission(permissionName, packageName, userId)
+        if (checkPermission(permName, packageName, userId)
                 == PackageManager.PERMISSION_GRANTED) {
             return false;
         }
@@ -6100,8 +5610,8 @@
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            flags = getPermissionFlags(permissionName,
-                    packageName, userId);
+            flags = mPermissionManager
+                    .getPermissionFlags(permName, packageName, Binder.getCallingUid(), userId);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -6118,27 +5628,6 @@
     }
 
     @Override
-    public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
-        mContext.enforceCallingOrSelfPermission(
-                Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS,
-                "addOnPermissionsChangeListener");
-
-        synchronized (mPackages) {
-            mOnPermissionChangeListeners.addListenerLocked(listener);
-        }
-    }
-
-    @Override
-    public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            throw new SecurityException("Instant applications don't have access to this method");
-        }
-        synchronized (mPackages) {
-            mOnPermissionChangeListeners.removeListenerLocked(listener);
-        }
-    }
-
-    @Override
     public boolean isProtectedBroadcast(String actionName) {
         // allow instant applications
         synchronized (mProtectedBroadcasts) {
@@ -6300,29 +5789,6 @@
     }
 
     /**
-     * This method should typically only be used when granting or revoking
-     * permissions, since the app may immediately restart after this call.
-     * <p>
-     * If you're doing surgery on app code/data, use {@link PackageFreezer} to
-     * guard your work against the app being relaunched.
-     */
-    private void killUid(int appId, int userId, String reason) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            IActivityManager am = ActivityManager.getService();
-            if (am != null) {
-                try {
-                    am.killUid(appId, userId, reason);
-                } catch (RemoteException e) {
-                    /* ignore - same process */
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
      * If the database version for this type of package (internal storage or
      * external storage) is less than the version where package signatures
      * were updated, return true.
@@ -9870,16 +9336,6 @@
     }
 
     @GuardedBy("mPackages")
-    public CheckPermissionDelegate getCheckPermissionDelegateLocked() {
-        return mCheckPermissionDelegate;
-    }
-
-    @GuardedBy("mPackages")
-    public void setCheckPermissionDelegateLocked(CheckPermissionDelegate delegate) {
-        mCheckPermissionDelegate = delegate;
-    }
-
-    @GuardedBy("mPackages")
     private void notifyPackageUseLocked(String packageName, int reason) {
         final PackageParser.Package p = mPackages.get(packageName);
         if (p == null) {
@@ -12336,7 +11792,7 @@
 
                 AsyncTask.execute(() ->
                         mPermissionManager.revokeRuntimePermissionsIfGroupChanged(pkg, oldPkg,
-                                allPackageNames, mPermissionCallback));
+                                allPackageNames));
             }
         }
 
@@ -13582,8 +13038,8 @@
                         != 0 && pkgSetting.pkg != null) {
                     whiteListedPermissions = pkgSetting.pkg.requestedPermissions;
                 }
-                setWhitelistedRestrictedPermissions(packageName, whiteListedPermissions,
-                        PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId);
+                mPermissionManager.setWhitelistedRestrictedPermissions(packageName,
+                        whiteListedPermissions, FLAG_PERMISSION_WHITELIST_INSTALLER, userId);
 
                 if (pkgSetting.pkg != null) {
                     synchronized (mInstallLock) {
@@ -16249,8 +15705,7 @@
         if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.codePath);
         synchronized (mPackages) {
 // NOTE: This changes slightly to include UPDATE_PERMISSIONS_ALL regardless of the size of pkg.permissions
-            mPermissionManager.updatePermissions(pkg.packageName, pkg, mPackages.values(),
-                    mPermissionCallback);
+            mPermissionManager.updatePermissions(pkg.packageName, pkg);
             // 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.
@@ -18919,8 +18374,7 @@
                     if (outInfo != null) {
                         outInfo.removedAppId = removedAppId;
                     }
-                    mPermissionManager.updatePermissions(
-                            deletedPs.name, null, mPackages.values(), mPermissionCallback);
+                    mPermissionManager.updatePermissions(deletedPs.name, null);
                     if (deletedPs.sharedUser != null) {
                         // Remove permissions associated with package. Since runtime
                         // permissions are per user we have to kill the removed package
@@ -19198,8 +18652,7 @@
             if (origPermissionState != null) {
                 ps.getPermissionsState().copyFrom(origPermissionState);
             }
-            mPermissionManager.updatePermissions(pkg.packageName, pkg, mPackages.values(),
-                    mPermissionCallback);
+            mPermissionManager.updatePermissions(pkg.packageName, pkg);
 
             final boolean applyUserRestrictions
                     = (allUserHandles != null) && (origUserHandles != null);
@@ -19661,9 +19114,7 @@
                     scheduleWritePackageRestrictionsLocked(nextUserId);
                 }
             }
-            synchronized (mPackages) {
-                resetUserChangesToRuntimePermissionsAndFlagsLPw(ps, nextUserId);
-            }
+            mPermissionManager.resetRuntimePermissions(pkg, nextUserId);
             // Also delete contributed media, when requested
             if ((flags & PackageManager.DELETE_CONTRIBUTED_MEDIA) != 0) {
                 try {
@@ -19774,15 +19225,12 @@
                     pkg = ps.pkg;
                 }
             }
-
-            if (pkg == null) {
-                Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
-                return false;
-            }
-
-            PackageSetting ps = (PackageSetting) pkg.mExtras;
-            resetUserChangesToRuntimePermissionsAndFlagsLPw(ps, userId);
         }
+        if (pkg == null) {
+            Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
+            return false;
+        }
+        mPermissionManager.resetRuntimePermissions(pkg, userId);
 
         clearAppDataLIF(pkg, userId,
                 FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL);
@@ -19804,201 +19252,11 @@
         return true;
     }
 
-    /**
-     * Reverts user permission state changes (permissions and flags) in
-     * all packages for a given user.
-     *
-     * @param userId The device user for which to do a reset.
-     */
-    @GuardedBy("mPackages")
-    private void resetUserChangesToRuntimePermissionsAndFlagsLPw(int userId) {
-        final int packageCount = mPackages.size();
-        for (int i = 0; i < packageCount; i++) {
-            PackageParser.Package pkg = mPackages.valueAt(i);
-            PackageSetting ps = (PackageSetting) pkg.mExtras;
-            resetUserChangesToRuntimePermissionsAndFlagsLPw(ps, userId);
-        }
-    }
-
     private void resetNetworkPolicies(int userId) {
         LocalServices.getService(NetworkPolicyManagerInternal.class).resetUserState(userId);
     }
 
     /**
-     * Reverts user permission state changes (permissions and flags).
-     *
-     * @param ps The package for which to reset.
-     * @param userId The device user for which to do a reset.
-     */
-    @GuardedBy("mPackages")
-    private void resetUserChangesToRuntimePermissionsAndFlagsLPw(
-            final PackageSetting ps, final int userId) {
-        if (ps.pkg == null) {
-            return;
-        }
-
-        final String packageName = ps.pkg.packageName;
-
-        // These are flags that can change base on user actions.
-        final int userSettableMask = FLAG_PERMISSION_USER_SET
-                | FLAG_PERMISSION_USER_FIXED
-                | FLAG_PERMISSION_REVOKE_ON_UPGRADE
-                | FLAG_PERMISSION_REVIEW_REQUIRED;
-
-        final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED
-                | FLAG_PERMISSION_POLICY_FIXED;
-
-        // Delay and combine non-async permission callbacks
-        final boolean[] permissionRemoved = new boolean[1];
-        final ArraySet<Long> revokedPermissions = new ArraySet<>();
-        final SparseBooleanArray updatedUsers = new SparseBooleanArray();
-
-        PermissionCallback delayingPermCallback = new PermissionCallback() {
-            public void onGidsChanged(int appId, int userId) {
-                mPermissionCallback.onGidsChanged(appId, userId);
-            }
-
-            public void onPermissionChanged() {
-                mPermissionCallback.onPermissionChanged();
-            }
-
-            public void onPermissionGranted(int uid, int userId) {
-                mPermissionCallback.onPermissionGranted(uid, userId);
-            }
-
-            public void onInstallPermissionGranted() {
-                mPermissionCallback.onInstallPermissionGranted();
-            }
-
-            public void onPermissionRevoked(int uid, int userId) {
-                revokedPermissions.add(IntPair.of(uid, userId));
-
-                updatedUsers.put(userId, true);
-            }
-
-            public void onInstallPermissionRevoked() {
-                mPermissionCallback.onInstallPermissionRevoked();
-            }
-
-            public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
-                for (int userId : updatedUserIds) {
-                    if (sync) {
-                        updatedUsers.put(userId, true);
-                    } else {
-                        // Don't override sync=true by sync=false
-                        if (!updatedUsers.get(userId)) {
-                            updatedUsers.put(userId, false);
-                        }
-                    }
-                }
-            }
-
-            public void onPermissionRemoved() {
-                permissionRemoved[0] = true;
-            }
-
-            public void onInstallPermissionUpdated() {
-                mPermissionCallback.onInstallPermissionUpdated();
-            }
-        };
-
-        final int permissionCount = ps.pkg.requestedPermissions.size();
-        for (int i = 0; i < permissionCount; i++) {
-            final String permName = ps.pkg.requestedPermissions.get(i);
-            final BasePermission bp =
-                    (BasePermission) mPermissionManager.getPermissionTEMP(permName);
-            if (bp == null) {
-                continue;
-            }
-
-            if (bp.isRemoved()) {
-                continue;
-            }
-
-            // If shared user we just reset the state to which only this app contributed.
-            if (ps.sharedUser != null) {
-                boolean used = false;
-                final int packageCount = ps.sharedUser.packages.size();
-                for (int j = 0; j < packageCount; j++) {
-                    PackageSetting pkg = ps.sharedUser.packages.valueAt(j);
-                    if (pkg.pkg != null && !pkg.pkg.packageName.equals(ps.pkg.packageName)
-                            && pkg.pkg.requestedPermissions.contains(permName)) {
-                        used = true;
-                        break;
-                    }
-                }
-                if (used) {
-                    continue;
-                }
-            }
-
-            final int oldFlags = mPermissionManager.getPermissionFlags(permName, packageName,
-                    Process.SYSTEM_UID, userId);
-
-            // Always clear the user settable flags.
-            // 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 (ps.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M && bp.isRuntime()) {
-                flags |= FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
-            }
-
-            mPermissionManager.updatePermissionFlags(permName, packageName,
-                    userSettableMask, flags, Process.SYSTEM_UID, userId, false,
-                    delayingPermCallback);
-
-            // Below is only runtime permission handling.
-            if (!bp.isRuntime()) {
-                continue;
-            }
-
-            // Never clobber system or policy.
-            if ((oldFlags & policyOrSystemFlags) != 0) {
-                continue;
-            }
-
-            // If this permission was granted by default, make sure it is.
-            if ((oldFlags & FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0) {
-                mPermissionManager.grantRuntimePermission(permName, packageName, false,
-                        Process.SYSTEM_UID, userId, delayingPermCallback);
-            // If permission review is enabled the permissions for a legacy apps
-            // are represented as constantly granted runtime ones, so don't revoke.
-            } else if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
-                // Otherwise, reset the permission.
-                mPermissionManager.revokeRuntimePermission(permName, packageName, false, userId,
-                        delayingPermCallback);
-            }
-        }
-
-        // Execute delayed callbacks
-        if (permissionRemoved[0]) {
-            mPermissionCallback.onPermissionRemoved();
-        }
-
-        // Slight variation on the code in mPermissionCallback.onPermissionRevoked() as we cannot
-        // kill uid while holding mPackages-lock
-        if (!revokedPermissions.isEmpty()) {
-            int numRevokedPermissions = revokedPermissions.size();
-            for (int i = 0; i < numRevokedPermissions; i++) {
-                int revocationUID = IntPair.first(revokedPermissions.valueAt(i));
-                int revocationUserId = IntPair.second(revokedPermissions.valueAt(i));
-
-                mOnPermissionChangeListeners.onPermissionsChanged(revocationUID);
-
-                // Kill app later as we are holding mPackages
-                mHandler.post(() -> killUid(UserHandle.getAppId(revocationUID), revocationUserId,
-                        KILL_APP_REASON_PERMISSIONS_REVOKED));
-            }
-        }
-
-        int numUpdatedUsers = updatedUsers.size();
-        for (int i = 0; i < numUpdatedUsers; i++) {
-            mSettings.writeRuntimePermissionsForUserLPr(updatedUsers.keyAt(i),
-                    updatedUsers.valueAt(i));
-        }
-    }
-
-    /**
      * Remove entries from the keystore daemon. Will only remove it if the
      * {@code appId} is valid.
      */
@@ -20454,8 +19712,8 @@
                 mSettings.applyDefaultPreferredAppsLPw(userId);
                 clearIntentFilterVerificationsLPw(userId);
                 primeDomainVerificationsLPw(userId);
-                resetUserChangesToRuntimePermissionsAndFlagsLPw(userId);
             }
+            mPermissionManager.resetAllRuntimePermissions(userId);
             updateDefaultHomeNotLocked(userId);
             // TODO: We have to reset the default SMS and Phone. This requires
             // significant refactoring to keep all default apps in the package
@@ -21667,9 +20925,7 @@
         // permissions, ensure permissions are updated. Beware of dragons if you
         // try optimizing this.
         synchronized (mPackages) {
-            mPermissionManager.updateAllPermissions(
-                    StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(),
-                    mPermissionCallback);
+            mPermissionManager.updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, false);
         }
 
         // Watch for external volumes that come and go over time
@@ -22662,8 +21918,7 @@
                 logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to "
                         + mSdkVersion + "; regranting permissions for " + volumeUuid);
             }
-            mPermissionManager.updateAllPermissions(volumeUuid, sdkUpdated, mPackages.values(),
-                    mPermissionCallback);
+            mPermissionManager.updateAllPermissions(volumeUuid, sdkUpdated);
 
             // Yay, everything is now upgraded
             ver.forceCurrent();
@@ -23691,9 +22946,7 @@
         mDefaultPermissionPolicy.grantDefaultPermissions(userId);
         synchronized(mPackages) {
             // NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG
-            mPermissionManager.updateAllPermissions(
-                    StorageManager.UUID_PRIVATE_INTERNAL, true, mPackages.values(),
-                    mPermissionCallback);
+            mPermissionManager.updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, true);
         }
     }
 
@@ -24027,59 +23280,6 @@
         }
     }
 
-    private final static class OnPermissionChangeListeners extends Handler {
-        private static final int MSG_ON_PERMISSIONS_CHANGED = 1;
-
-        private final RemoteCallbackList<IOnPermissionsChangeListener> mPermissionListeners =
-                new RemoteCallbackList<>();
-
-        public OnPermissionChangeListeners(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_ON_PERMISSIONS_CHANGED: {
-                    final int uid = msg.arg1;
-                    handleOnPermissionsChanged(uid);
-                } break;
-            }
-        }
-
-        public void addListenerLocked(IOnPermissionsChangeListener listener) {
-            mPermissionListeners.register(listener);
-
-        }
-
-        public void removeListenerLocked(IOnPermissionsChangeListener listener) {
-            mPermissionListeners.unregister(listener);
-        }
-
-        public void onPermissionsChanged(int uid) {
-            if (mPermissionListeners.getRegisteredCallbackCount() > 0) {
-                obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0).sendToTarget();
-            }
-        }
-
-        private void handleOnPermissionsChanged(int uid) {
-            final int count = mPermissionListeners.beginBroadcast();
-            try {
-                for (int i = 0; i < count; i++) {
-                    IOnPermissionsChangeListener callback = mPermissionListeners
-                            .getBroadcastItem(i);
-                    try {
-                        callback.onPermissionsChanged(uid);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Permission listener is dead", e);
-                    }
-                }
-            } finally {
-                mPermissionListeners.finishBroadcast();
-            }
-        }
-    }
-
     private class PackageManagerNative extends IPackageManagerNative.Stub {
         @Override
         public String[] getNamesForUids(int[] uids) throws RemoteException {
@@ -24171,13 +23371,6 @@
 
     private class PackageManagerInternalImpl extends PackageManagerInternal {
         @Override
-        public void updatePermissionFlagsTEMP(String permName, String packageName, int flagMask,
-                int flagValues, int userId) {
-            PackageManagerService.this.updatePermissionFlags(
-                    permName, packageName, flagMask, flagValues, true, userId);
-        }
-
-        @Override
         public List<ApplicationInfo> getInstalledApplications(int flags, int userId,
                 int callingUid) {
             return PackageManagerService.this.getInstalledApplicationsListInternal(flags, userId,
@@ -24258,11 +23451,6 @@
         }
 
         @Override
-        public int getPermissionFlagsTEMP(String permName, String packageName, int userId) {
-            return PackageManagerService.this.getPermissionFlags(permName, packageName, userId);
-        }
-
-        @Override
         public boolean isInstantApp(String packageName, int userId) {
             return PackageManagerService.this.isInstantApp(packageName, userId);
         }
@@ -24290,6 +23478,19 @@
         }
 
         @Override
+        public PackageParser.Package getPackage(int uid) {
+            synchronized (mPackages) {
+                final String[] packageNames = getPackagesForUid(uid);
+                PackageParser.Package pkg = null;
+                final int numPackages = packageNames == null ? 0 : packageNames.length;
+                for (int i = 0; pkg == null && i < numPackages; i++) {
+                    pkg = mPackages.get(packageNames[i]);
+                }
+                return pkg;
+            }
+        }
+
+        @Override
         public PackageList getPackageList(PackageListObserver observer) {
             synchronized (mPackages) {
                 final int N = mPackages.size();
@@ -24595,22 +23796,6 @@
         }
 
         @Override
-        public void grantRuntimePermission(String packageName, String permName, int userId,
-                boolean overridePolicy) {
-            PackageManagerService.this.mPermissionManager.grantRuntimePermission(
-                    permName, packageName, overridePolicy, getCallingUid(), userId,
-                    mPermissionCallback);
-        }
-
-        @Override
-        public void revokeRuntimePermission(String packageName, String permName, int userId,
-                boolean overridePolicy) {
-            mPermissionManager.revokeRuntimePermission(
-                    permName, packageName, overridePolicy, userId,
-                    mPermissionCallback);
-        }
-
-        @Override
         public String getNameForUid(int uid) {
             return PackageManagerService.this.getNameForUid(uid);
         }
@@ -24813,20 +23998,6 @@
         }
 
         @Override
-        public CheckPermissionDelegate getCheckPermissionDelegate() {
-            synchronized (mPackages) {
-                return PackageManagerService.this.getCheckPermissionDelegateLocked();
-            }
-        }
-
-        @Override
-        public void setCheckPermissionDelegate(CheckPermissionDelegate delegate) {
-            synchronized (mPackages) {
-                PackageManagerService.this.setCheckPermissionDelegateLocked(delegate);
-            }
-        }
-
-        @Override
         public SparseArray<String> getAppsWithSharedUserIds() {
             synchronized (mPackages) {
                 return getAppsWithSharedUserIdsLocked();
@@ -25036,6 +24207,65 @@
                 }
             }
         }
+
+        @Override
+        public void writePermissionSettings(int[] userIds, boolean async) {
+            synchronized (mPackages) {
+                for (int userId : userIds) {
+                    mSettings.writeRuntimePermissionsForUserLPr(userId, !async);
+                }
+            }
+        }
+
+        @Override
+        public int getTargetSdk(int uid) {
+            int userId = UserHandle.getUserId(uid);
+
+            synchronized (mPackages) {
+                final Object obj = mSettings.getSettingLPr(UserHandle.getAppId(uid));
+                if (obj instanceof PackageSetting) {
+                    final PackageSetting ps = (PackageSetting) obj;
+                    if (!ps.getInstalled(userId)) {
+                        return 0;
+                    }
+                    return ps.pkg.applicationInfo.targetSdkVersion;
+                } else if (obj instanceof SharedUserSetting) {
+                    int maxTargetSdk = 0;
+                    final SharedUserSetting sus = (SharedUserSetting) obj;
+                    final int numPkgs = sus.packages.size();
+                    for (int i = 0; i < numPkgs; i++) {
+                        final PackageSetting ps = sus.packages.valueAt(i);
+                        if (!ps.getInstalled(userId)) {
+                            continue;
+                        }
+                        if (ps.pkg.applicationInfo.targetSdkVersion < maxTargetSdk) {
+                            continue;
+                        }
+                        maxTargetSdk = ps.pkg.applicationInfo.targetSdkVersion;
+                    }
+                    return maxTargetSdk;
+                }
+                return 0;
+            }
+        }
+
+        @Override
+        public boolean isCallerInstallerOfRecord(
+                @NonNull PackageParser.Package pkg, int callingUid) {
+            synchronized (mPackages) {
+                if (pkg == null) {
+                    return false;
+                }
+                final PackageSetting packageSetting = (PackageSetting) pkg.mExtras;
+                if (packageSetting == null) {
+                    return false;
+                }
+                final PackageSetting installerPackageSetting =
+                        mSettings.mPackages.get(packageSetting.installerPackageName);
+                return installerPackageSetting != null
+                        && UserHandle.isSameApp(installerPackageSetting.appId, callingUid);
+            }
+        }
     }
 
     @GuardedBy("mPackages")
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 45b6834..f56e1ef 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -62,6 +62,7 @@
 import com.android.server.EventLogTags;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.PackageDexUsage;
+import com.android.server.pm.permission.PermissionsState;
 
 import dalvik.system.VMRuntime;
 
@@ -885,4 +886,16 @@
             IoUtils.closeQuietly(source);
         }
     }
+
+    /**
+     * Returns the {@link PermissionsState} for the given package. If the {@link PermissionsState}
+     * could not be found, {@code null} will be returned.
+     */
+    public static PermissionsState getPermissionsState(PackageParser.Package pkg) {
+        final PackageSetting packageSetting = (PackageSetting) pkg.mExtras;
+        if (packageSetting == null) {
+            return null;
+        }
+        return packageSetting.getPermissionsState();
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index a25c68f..53c1c1f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1997,15 +1997,15 @@
         }
 
         if (grant) {
-            mInterface.grantRuntimePermission(pkg, perm, userId);
+            mPermissionManager.grantRuntimePermission(pkg, perm, userId);
         } else {
-            mInterface.revokeRuntimePermission(pkg, perm, userId);
+            mPermissionManager.revokeRuntimePermission(pkg, perm, userId);
         }
         return 0;
     }
 
     private int runResetPermissions() throws RemoteException {
-        mInterface.resetRuntimePermissions();
+        mPermissionManager.resetRuntimePermissions();
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 15dc6ae..e375fa4 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -8,6 +8,14 @@
     },
     {
       "name": "CtsCompilationTestCases"
+    },
+    {
+      "name": "CtsPermissionTestCases",
+      "options": [
+        {
+            "include-filter": "android.permission.cts.PermissionUpdateListenerTest"
+        }
+      ]
     }
   ],
   "imports": [
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 9b00dca..9d0b427 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm.permission;
 
+import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY;
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
 import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
@@ -33,12 +34,15 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE;
 import static android.content.pm.PackageManager.MASK_PERMISSION_FLAGS_ALL;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.permission.PermissionManager.KILL_APP_REASON_GIDS_CHANGED;
+import static android.permission.PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED;
 
 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 com.android.server.pm.permission.PermissionManagerService.killUid;
 import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
 
 import static java.util.concurrent.TimeUnit.SECONDS;
@@ -48,7 +52,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.ApplicationPackageManager;
+import android.app.IActivityManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.PermissionGroupInfoFlags;
@@ -65,7 +71,11 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
 import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -73,16 +83,19 @@
 import android.os.UserManagerInternal;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageManagerInternal;
+import android.permission.IOnPermissionsChangeListener;
 import android.permission.IPermissionManager;
 import android.permission.PermissionControllerManager;
 import android.permission.PermissionManager;
 import android.permission.PermissionManagerInternal;
+import android.permission.PermissionManagerInternal.CheckPermissionDelegate;
 import android.permission.PermissionManagerInternal.OnRuntimePermissionStateChangedListener;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DebugUtils;
 import android.util.EventLog;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -93,6 +106,8 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.RoSystemProperties;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IntPair;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
@@ -112,7 +127,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -123,6 +137,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
 
 /**
  * Manages all permissions and handles permissions related tasks.
@@ -229,6 +244,70 @@
     final private ArrayList<OnRuntimePermissionStateChangedListener>
             mRuntimePermissionStateChangedListeners = new ArrayList<>();
 
+    @GuardedBy("mLock")
+    private CheckPermissionDelegate mCheckPermissionDelegate;
+
+    @GuardedBy("mLock")
+    private final OnPermissionChangeListeners mOnPermissionChangeListeners;
+
+    // TODO: Take a look at the methods defined in the callback.
+    // The callback was initially created to support the split between permission
+    // manager and the package manager. However, it's started to be used for other
+    // purposes. It may make sense to keep as an abstraction, but, the methods
+    // necessary to be overridden may be different than what was initially needed
+    // for the split.
+    private PermissionCallback mDefaultPermissionCallback = new PermissionCallback() {
+        @Override
+        public void onGidsChanged(int appId, int userId) {
+            mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED));
+        }
+        @Override
+        public void onPermissionGranted(int uid, int userId) {
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+
+            // Not critical; if this is lost, the application has to request again.
+            mPackageManagerInt.writeSettings(true);
+        }
+        @Override
+        public void onInstallPermissionGranted() {
+            mPackageManagerInt.writeSettings(true);
+        }
+        @Override
+        public void onPermissionRevoked(int uid, int userId) {
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+
+            // Critical; after this call the application should never have the permission
+            mPackageManagerInt.writeSettings(false);
+            final int appId = UserHandle.getAppId(uid);
+            killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
+        }
+        @Override
+        public void onInstallPermissionRevoked() {
+            mPackageManagerInt.writeSettings(true);
+        }
+        @Override
+        public void onPermissionUpdated(int[] userIds, boolean sync) {
+            mPackageManagerInt.writePermissionSettings(userIds, !sync);
+        }
+        @Override
+        public void onInstallPermissionUpdated() {
+            mPackageManagerInt.writeSettings(true);
+        }
+        @Override
+        public void onPermissionRemoved() {
+            mPackageManagerInt.writeSettings(false);
+        }
+        public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
+                int uid) {
+            onPermissionUpdated(updatedUserIds, sync);
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+        }
+        public void onInstallPermissionUpdatedNotifyListener(int uid) {
+            onInstallPermissionUpdated();
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+        }
+    };
+
     PermissionManagerService(Context context,
             @NonNull Object externalLock) {
         mContext = context;
@@ -248,6 +327,7 @@
         SystemConfig systemConfig = SystemConfig.getInstance();
         mSystemPermissions = systemConfig.getSystemPermissions();
         mGlobalGids = systemConfig.getGlobalGids();
+        mOnPermissionChangeListeners = new OnPermissionChangeListeners(FgThread.get().getLooper());
 
         // propagate permission configuration
         final ArrayMap<String, SystemConfig.PermissionEntry> permConfig =
@@ -298,6 +378,29 @@
         return LocalServices.getService(PermissionManagerServiceInternal.class);
     }
 
+    /**
+     * This method should typically only be used when granting or revoking
+     * permissions, since the app may immediately restart after this call.
+     * <p>
+     * If you're doing surgery on app code/data, use {@link PackageFreezer} to
+     * guard your work against the app being relaunched.
+     */
+    public static void killUid(int appId, int userId, String reason) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            IActivityManager am = ActivityManager.getService();
+            if (am != null) {
+                try {
+                    am.killUid(appId, userId, reason);
+                } catch (RemoteException e) {
+                    /* ignore - same process */
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
     @Nullable BasePermission getPermission(String permName) {
         synchronized (mLock) {
             return mSettings.getPermissionLocked(permName);
@@ -456,12 +559,232 @@
         }
     }
 
-    private int checkPermission(String permName, String pkgName, int callingUid, int userId) {
+    @Override
+    public int getPermissionFlags(String permName, String packageName, int userId) {
+        final int callingUid = getCallingUid();
+        return getPermissionFlagsInternal(permName, packageName, callingUid, userId);
+    }
+
+    private int getPermissionFlagsInternal(
+            String permName, String packageName, int callingUid, int userId) {
+        if (!mUserManagerInt.exists(userId)) {
+            return 0;
+        }
+
+        enforceGrantRevokeGetRuntimePermissionPermissions("getPermissionFlags");
+        enforceCrossUserPermission(callingUid, userId,
+                true,  // requireFullPermission
+                false, // checkShell
+                false, // requirePermissionWhenSameUser
+                "getPermissionFlags");
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            return 0;
+        }
+        synchronized (mLock) {
+            if (mSettings.getPermissionLocked(permName) == null) {
+                return 0;
+            }
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            return 0;
+        }
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        PermissionsState permissionsState = ps.getPermissionsState();
+        return permissionsState.getPermissionFlags(permName, userId);
+    }
+
+    @Override
+    public void updatePermissionFlags(String permName, String packageName, int flagMask,
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+        final int callingUid = getCallingUid();
+        boolean overridePolicy = false;
+
+        if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) {
+            long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0) {
+                    if (checkAdjustPolicyFlagPermission) {
+                        mContext.enforceCallingOrSelfPermission(
+                                Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
+                                "Need " + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY
+                                        + " to change policy flags");
+                    } else if (mPackageManagerInt.getTargetSdk(callingUid)
+                            >= Build.VERSION_CODES.Q) {
+                        throw new IllegalArgumentException(
+                                Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY + " needs "
+                                        + " to be checked for packages targeting "
+                                        + Build.VERSION_CODES.Q + " or later when changing policy "
+                                        + "flags");
+                    }
+                    overridePolicy = true;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        updatePermissionFlagsInternal(
+                permName, packageName, flagMask, flagValues, callingUid, userId,
+                overridePolicy, mDefaultPermissionCallback);
+    }
+
+    private void updatePermissionFlagsInternal(String permName, String packageName, int flagMask,
+            int flagValues, int callingUid, int userId, boolean overridePolicy,
+            PermissionCallback callback) {
+        if (ApplicationPackageManager.DEBUG_TRACE_GRANTS
+                && ApplicationPackageManager.shouldTraceGrant(packageName, permName, userId)) {
+            Log.i(TAG, "System is updating flags for "
+                            + permName + " for user " + userId  + " "
+                            + DebugUtils.flagsToString(
+                                    PackageManager.class, "FLAG_PERMISSION_", flagMask)
+                            + " := "
+                            + DebugUtils.flagsToString(
+                                    PackageManager.class, "FLAG_PERMISSION_", flagValues)
+                            + " on behalf of uid " + callingUid
+                            + " " + mPackageManagerInt.getNameForUid(callingUid),
+                    new RuntimeException());
+        }
+
+        if (!mUserManagerInt.exists(userId)) {
+            return;
+        }
+
+        enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true,  // requireFullPermission
+                true,  // checkShell
+                false, // requirePermissionWhenSameUser
+                "updatePermissionFlags");
+
+        if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0 && !overridePolicy) {
+            throw new SecurityException("updatePermissionFlags requires "
+                    + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
+        }
+
+        // Only the system can change these flags and nothing else.
+        if (callingUid != Process.SYSTEM_UID) {
+            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+            flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
+        }
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            Log.e(TAG, "Unknown package: " + packageName);
+            return;
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+
+        final BasePermission bp;
+        synchronized (mLock) {
+            bp = mSettings.getPermissionLocked(permName);
+        }
+        if (bp == null) {
+            throw new IllegalArgumentException("Unknown permission: " + permName);
+        }
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+        final boolean hadState =
+                permissionsState.getRuntimePermissionState(permName, userId) != null;
+        final boolean permissionUpdated =
+                permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues);
+        if (permissionUpdated && bp.isRuntime()) {
+            notifyRuntimePermissionStateChanged(packageName, userId);
+        }
+        if (permissionUpdated && callback != null) {
+            // Install and runtime permissions are stored in different places,
+            // so figure out what permission changed and persist the change.
+            if (permissionsState.getInstallPermissionState(permName) != null) {
+                callback.onInstallPermissionUpdatedNotifyListener(pkg.applicationInfo.uid);
+            } else if (permissionsState.getRuntimePermissionState(permName, userId) != null
+                    || hadState) {
+                callback.onPermissionUpdatedNotifyListener(new int[] { userId }, false,
+                        pkg.applicationInfo.uid);
+            }
+        }
+    }
+
+    /**
+     * Update the permission flags for all packages and runtime permissions of a user in order
+     * to allow device or profile owner to remove POLICY_FIXED.
+     */
+    @Override
+    public void updatePermissionFlagsForAllApps(int flagMask, int flagValues,
+            final int userId) {
+        final int callingUid = getCallingUid();
+        if (!mUserManagerInt.exists(userId)) {
+            return;
+        }
+
+        enforceGrantRevokeRuntimePermissionPermissions(
+                "updatePermissionFlagsForAllApps");
+        enforceCrossUserPermission(callingUid, userId,
+                true,  // requireFullPermission
+                true,  // checkShell
+                false, // requirePermissionWhenSameUser
+                "updatePermissionFlagsForAllApps");
+
+        // Only the system can change system fixed flags.
+        final int effectiveFlagMask = (callingUid != Process.SYSTEM_UID)
+                ? flagMask : flagMask & ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+        final int effectiveFlagValues = (callingUid != Process.SYSTEM_UID)
+                ? flagValues : flagValues & ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+
+        final boolean[] changed = new boolean[1];
+        mPackageManagerInt.forEachPackage(new Consumer<PackageParser.Package>() {
+            @Override
+            public void accept(Package pkg) {
+                final PackageSetting ps = (PackageSetting) pkg.mExtras;
+                if (ps == null) {
+                    return;
+                }
+                final PermissionsState permissionsState = ps.getPermissionsState();
+                changed[0] |= permissionsState.updatePermissionFlagsForAllPermissions(
+                        userId, effectiveFlagMask, effectiveFlagValues);
+                mOnPermissionChangeListeners.onPermissionsChanged(pkg.applicationInfo.uid);
+            }
+        });
+
+        if (changed[0]) {
+            mPackageManagerInt.writePermissionSettings(new int[] { userId }, true);
+        }
+    }
+
+    @Override
+    public int checkPermission(String permName, String pkgName, int userId) {
+        final CheckPermissionDelegate checkPermissionDelegate;
+        synchronized (mLock) {
+            if (mCheckPermissionDelegate == null)  {
+                return checkPermissionImpl(permName, pkgName, userId);
+            }
+            checkPermissionDelegate = mCheckPermissionDelegate;
+        }
+        return checkPermissionDelegate.checkPermission(permName, pkgName, userId,
+                PermissionManagerService.this::checkPermissionImpl);
+    }
+
+    private int checkPermissionImpl(String permName, String pkgName, int userId) {
+        return checkPermissionInternal(permName, pkgName, getCallingUid(), userId);
+    }
+
+    private int checkPermissionInternal(String permName, String packageName, int callingUid,
+            int userId) {
         if (!mUserManagerInt.exists(userId)) {
             return PackageManager.PERMISSION_DENIED;
         }
-
-        final PackageParser.Package pkg = mPackageManagerInt.getPackage(pkgName);
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
         if (pkg != null && pkg.mExtras != null) {
             if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
                 return PackageManager.PERMISSION_DENIED;
@@ -485,12 +808,38 @@
                 return PackageManager.PERMISSION_GRANTED;
             }
         }
-
         return PackageManager.PERMISSION_DENIED;
     }
 
-    private int checkUidPermission(String permName, PackageParser.Package pkg, int uid,
-            int callingUid) {
+    @Override
+    public int checkUidPermission(String permName, int uid) {
+        final CheckPermissionDelegate checkPermissionDelegate;
+        synchronized (mLock) {
+            if (mCheckPermissionDelegate == null)  {
+                return checkUidPermissionImpl(permName, uid);
+            }
+            checkPermissionDelegate = mCheckPermissionDelegate;
+        }
+        return checkPermissionDelegate.checkUidPermission(permName, uid,
+                PermissionManagerService.this::checkUidPermissionImpl);
+    }
+
+    private int checkUidPermissionImpl(@NonNull String permName, int uid) {
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(uid);
+        synchronized (mLock) {
+            return checkUidPermissionInternal(permName, pkg, uid, getCallingUid());
+        }
+    }
+
+    /**
+     * Checks whether or not the given package has been granted the specified
+     * permission. If the given package is {@code null}, we instead check the
+     * system permissions for the given UID.
+     *
+     * @see SystemConfig#getSystemPermissions()
+     */
+    private int checkUidPermissionInternal(@NonNull String permName,
+            @Nullable PackageParser.Package pkg, int uid, int callingUid) {
         final int callingUserId = UserHandle.getUserId(callingUid);
         final boolean isCallerInstantApp =
                 mPackageManagerInt.getInstantAppPackageName(callingUid) != null;
@@ -538,6 +887,728 @@
         return PackageManager.PERMISSION_DENIED;
     }
 
+    @Override
+    public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS,
+                "addOnPermissionsChangeListener");
+
+        synchronized (mLock) {
+            mOnPermissionChangeListeners.addListenerLocked(listener);
+        }
+    }
+
+    @Override
+    public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
+        if (mPackageManagerInt.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+            throw new SecurityException("Instant applications don't have access to this method");
+        }
+        synchronized (mLock) {
+            mOnPermissionChangeListeners.removeListenerLocked(listener);
+        }
+    }
+
+    @Override
+    @Nullable public List<String> getWhitelistedRestrictedPermissions(@NonNull String packageName,
+            @PermissionWhitelistFlags int flags, @UserIdInt int userId) {
+        Preconditions.checkNotNull(packageName);
+        Preconditions.checkFlagsArgument(flags,
+                PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+                        | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+                        | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+        Preconditions.checkArgumentNonNegative(userId, null);
+
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS,
+                    "getWhitelistedRestrictedPermissions for user " + userId);
+        }
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null) {
+            return null;
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, UserHandle.getCallingUserId())) {
+            return null;
+        }
+        final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission(
+                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
+                        == PackageManager.PERMISSION_GRANTED;
+        final boolean isCallerInstallerOnRecord =
+                mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
+
+        if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0
+                && !isCallerPrivileged) {
+            throw new SecurityException("Querying system whitelist requires "
+                    + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
+        }
+
+        if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+                | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) != 0) {
+            if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
+                throw new SecurityException("Querying upgrade or installer whitelist"
+                        + " requires being installer on record or "
+                        + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
+            }
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final PermissionsState permissionsState =
+                    PackageManagerServiceUtils.getPermissionsState(pkg);
+            if (permissionsState == null) {
+                return null;
+            }
+
+            int queryFlags = 0;
+            if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0) {
+                queryFlags |= PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+            }
+            if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) {
+                queryFlags |= PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+            }
+            if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
+                queryFlags |=  PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+            }
+
+            ArrayList<String> whitelistedPermissions = null;
+
+            final int permissionCount = pkg.requestedPermissions.size();
+            for (int i = 0; i < permissionCount; i++) {
+                final String permissionName = pkg.requestedPermissions.get(i);
+                final int currentFlags =
+                        permissionsState.getPermissionFlags(permissionName, userId);
+                if ((currentFlags & queryFlags) != 0) {
+                    if (whitelistedPermissions == null) {
+                        whitelistedPermissions = new ArrayList<>();
+                    }
+                    whitelistedPermissions.add(permissionName);
+                }
+            }
+
+            return whitelistedPermissions;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public boolean addWhitelistedRestrictedPermission(@NonNull String packageName,
+            @NonNull String permName, @PermissionWhitelistFlags int flags,
+            @UserIdInt int userId) {
+        // Other argument checks are done in get/setWhitelistedRestrictedPermissions
+        Preconditions.checkNotNull(permName);
+
+        if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permName)) {
+            return false;
+        }
+
+        List<String> permissions =
+                getWhitelistedRestrictedPermissions(packageName, flags, userId);
+        if (permissions == null) {
+            permissions = new ArrayList<>(1);
+        }
+        if (permissions.indexOf(permName) < 0) {
+            permissions.add(permName);
+            return setWhitelistedRestrictedPermissionsInternal(packageName, permissions,
+                    flags, userId);
+        }
+        return false;
+    }
+
+    private boolean checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(
+            @NonNull String permName) {
+        synchronized (mLock) {
+            final BasePermission bp = mSettings.getPermissionLocked(permName);
+            if (bp == null) {
+                Slog.w(TAG, "No such permissions: " + permName);
+                return false;
+            }
+            if (bp.isHardOrSoftRestricted() && bp.isImmutablyRestricted()
+                    && mContext.checkCallingOrSelfPermission(
+                    Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Cannot modify whitelisting of an immutably "
+                        + "restricted permission: " + permName);
+            }
+            return true;
+        }
+    }
+
+    @Override
+    public boolean removeWhitelistedRestrictedPermission(@NonNull String packageName,
+            @NonNull String permName, @PermissionWhitelistFlags int flags,
+            @UserIdInt int userId) {
+        // Other argument checks are done in get/setWhitelistedRestrictedPermissions
+        Preconditions.checkNotNull(permName);
+
+        if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permName)) {
+            return false;
+        }
+
+        final List<String> permissions =
+                getWhitelistedRestrictedPermissions(packageName, flags, userId);
+        if (permissions != null && permissions.remove(permName)) {
+            return setWhitelistedRestrictedPermissionsInternal(packageName, permissions,
+                    flags, userId);
+        }
+        return false;
+    }
+
+    private boolean setWhitelistedRestrictedPermissionsInternal(@NonNull String packageName,
+            @Nullable List<String> permissions, @PermissionWhitelistFlags int flags,
+            @UserIdInt int userId) {
+        Preconditions.checkNotNull(packageName);
+        Preconditions.checkFlagsArgument(flags,
+                PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+                        | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+                        | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+        Preconditions.checkArgument(Integer.bitCount(flags) == 1);
+        Preconditions.checkArgumentNonNegative(userId, null);
+
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS,
+                    "setWhitelistedRestrictedPermissions for user " + userId);
+        }
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null) {
+            return false;
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, UserHandle.getCallingUserId())) {
+            return false;
+        }
+
+        final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission(
+                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
+                        == PackageManager.PERMISSION_GRANTED;
+        final boolean isCallerInstallerOnRecord =
+                mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
+
+        if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0
+                && !isCallerPrivileged) {
+            throw new SecurityException("Modifying system whitelist requires "
+                    + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
+        }
+
+        if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) {
+            if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
+                throw new SecurityException("Modifying upgrade whitelist requires"
+                        + " being installer on record or "
+                        + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
+            }
+            final List<String> whitelistedPermissions =
+                    getWhitelistedRestrictedPermissions(pkg.packageName, flags, userId);
+            if (permissions == null || permissions.isEmpty()) {
+                if (whitelistedPermissions == null || whitelistedPermissions.isEmpty()) {
+                    return true;
+                }
+            } else {
+                // Only the system can add and remove while the installer can only remove.
+                final int permissionCount = permissions.size();
+                for (int i = 0; i < permissionCount; i++) {
+                    if ((whitelistedPermissions == null
+                            || !whitelistedPermissions.contains(permissions.get(i)))
+                            && !isCallerPrivileged) {
+                        throw new SecurityException("Adding to upgrade whitelist requires"
+                                + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
+                    }
+                }
+            }
+
+            if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
+                if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
+                    throw new SecurityException("Modifying installer whitelist requires"
+                            + " being installer on record or "
+                            + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
+                }
+            }
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            setWhitelistedRestrictedPermissionsForUser(
+                    pkg, userId, permissions, Process.myUid(), flags, mDefaultPermissionCallback);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        return true;
+    }
+
+    @Override
+    public void grantRuntimePermission(String packageName, String permName, final int userId) {
+        final int callingUid = Binder.getCallingUid();
+        final boolean overridePolicy =
+                checkUidPermission(ADJUST_RUNTIME_PERMISSIONS_POLICY, callingUid)
+                        == PackageManager.PERMISSION_GRANTED;
+
+        grantRuntimePermissionInternal(permName, packageName, overridePolicy,
+                callingUid, userId, mDefaultPermissionCallback);
+    }
+
+    // TODO swap permission name and package name
+    private void grantRuntimePermissionInternal(String permName, String packageName,
+            boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) {
+        if (ApplicationPackageManager.DEBUG_TRACE_GRANTS
+                && ApplicationPackageManager.shouldTraceGrant(packageName, permName, userId)) {
+            Log.i(TAG, "System is granting "
+                    + permName + " for user " + userId + " on behalf of uid " + callingUid
+                    + " " + mPackageManagerInt.getNameForUid(callingUid),
+                    new RuntimeException());
+        }
+        if (!mUserManagerInt.exists(userId)) {
+            Log.e(TAG, "No such user:" + userId);
+            return;
+        }
+
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+                "grantRuntimePermission");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true,  // requireFullPermission
+                true,  // checkShell
+                false, // requirePermissionWhenSameUser
+                "grantRuntimePermission");
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        final BasePermission bp;
+        synchronized (mLock) {
+            bp = mSettings.getPermissionLocked(permName);
+        }
+        if (bp == null) {
+            throw new IllegalArgumentException("Unknown permission: " + permName);
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+
+        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg);
+
+        // 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 (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+                && bp.isRuntime()) {
+            return;
+        }
+
+        final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+
+        final int flags = permissionsState.getPermissionFlags(permName, userId);
+        if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+            Log.e(TAG, "Cannot grant system fixed permission "
+                    + permName + " for package " + packageName);
+            return;
+        }
+        if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+            Log.e(TAG, "Cannot grant policy fixed permission "
+                    + permName + " for package " + packageName);
+            return;
+        }
+
+        if (bp.isHardRestricted()
+                && (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) {
+            Log.e(TAG, "Cannot grant hard restricted non-exempt permission "
+                    + permName + " for package " + packageName);
+            return;
+        }
+
+        if (bp.isSoftRestricted() && !SoftRestrictedPermissionPolicy.forPermission(mContext,
+                pkg.applicationInfo, UserHandle.of(userId), permName).canBeGranted()) {
+            Log.e(TAG, "Cannot grant soft restricted permission " + permName + " for package "
+                    + packageName);
+            return;
+        }
+
+        if (bp.isDevelopment()) {
+            // Development permissions must be handled specially, since they are not
+            // normal runtime permissions.  For now they apply to all users.
+            if (permissionsState.grantInstallPermission(bp)
+                    != PERMISSION_OPERATION_FAILURE) {
+                if (callback != null) {
+                    callback.onInstallPermissionGranted();
+                }
+            }
+            return;
+        }
+
+        if (ps.getInstantApp(userId) && !bp.isInstant()) {
+            throw new SecurityException("Cannot grant non-ephemeral permission"
+                    + permName + " for package " + packageName);
+        }
+
+        if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+            Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
+            return;
+        }
+
+        final int result = permissionsState.grantRuntimePermission(bp, userId);
+        switch (result) {
+            case PERMISSION_OPERATION_FAILURE: {
+                return;
+            }
+
+            case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
+                if (callback != null) {
+                    callback.onGidsChanged(UserHandle.getAppId(pkg.applicationInfo.uid), userId);
+                }
+            }
+            break;
+        }
+
+        if (bp.isRuntime()) {
+            logPermission(MetricsEvent.ACTION_PERMISSION_GRANTED, permName, packageName);
+        }
+
+        if (callback != null) {
+            callback.onPermissionGranted(uid, userId);
+        }
+
+        if (bp.isRuntime()) {
+            notifyRuntimePermissionStateChanged(packageName, userId);
+        }
+
+        // Only need to do this if user is initialized. Otherwise it's a new user
+        // and there are no processes running as the user yet and there's no need
+        // to make an expensive call to remount processes for the changed permissions.
+        if (READ_EXTERNAL_STORAGE.equals(permName)
+                || WRITE_EXTERNAL_STORAGE.equals(permName)) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (mUserManagerInt.isUserInitialized(userId)) {
+                    StorageManagerInternal storageManagerInternal = LocalServices.getService(
+                            StorageManagerInternal.class);
+                    storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+    }
+
+    @Override
+    public void revokeRuntimePermission(String packageName, String permName, int userId) {
+        final int callingUid = Binder.getCallingUid();
+        final boolean overridePolicy =
+                checkUidPermission(ADJUST_RUNTIME_PERMISSIONS_POLICY, callingUid)
+                        == PackageManager.PERMISSION_GRANTED;
+
+        revokeRuntimePermissionInternal(permName, packageName, overridePolicy, callingUid, userId,
+                mDefaultPermissionCallback);
+    }
+
+    // TODO swap permission name and package name
+    private void revokeRuntimePermissionInternal(String permName, String packageName,
+            boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) {
+        if (ApplicationPackageManager.DEBUG_TRACE_GRANTS
+                && ApplicationPackageManager.shouldTraceGrant(packageName, permName, userId)) {
+            Log.i(TAG, "System is revoking "
+                            + permName + " for user " + userId + " on behalf of uid " + callingUid
+                            + " " + mPackageManagerInt.getNameForUid(callingUid),
+                    new RuntimeException());
+        }
+        if (!mUserManagerInt.exists(userId)) {
+            Log.e(TAG, "No such user:" + userId);
+            return;
+        }
+
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+                "revokeRuntimePermission");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true,  // requireFullPermission
+                true,  // checkShell
+                false, // requirePermissionWhenSameUser
+                "revokeRuntimePermission");
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        final BasePermission bp = mSettings.getPermissionLocked(permName);
+        if (bp == null) {
+            throw new IllegalArgumentException("Unknown permission: " + permName);
+        }
+
+        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg);
+
+        // 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 (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+                && bp.isRuntime()) {
+            return;
+        }
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+
+        final int flags = permissionsState.getPermissionFlags(permName, userId);
+        // Only the system may revoke SYSTEM_FIXED permissions.
+        if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
+                && UserHandle.getCallingAppId() != Process.SYSTEM_UID) {
+            throw new SecurityException("Non-System UID cannot revoke system fixed permission "
+                    + permName + " for package " + packageName);
+        }
+        if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+            throw new SecurityException("Cannot revoke policy fixed permission "
+                    + permName + " for package " + packageName);
+        }
+
+        if (bp.isDevelopment()) {
+            // Development permissions must be handled specially, since they are not
+            // normal runtime permissions.  For now they apply to all users.
+            if (permissionsState.revokeInstallPermission(bp)
+                    != PERMISSION_OPERATION_FAILURE) {
+                if (callback != null) {
+                    mDefaultPermissionCallback.onInstallPermissionRevoked();
+                }
+            }
+            return;
+        }
+
+        // Permission is already revoked, no need to do anything.
+        if (!permissionsState.hasRuntimePermission(permName, userId)) {
+            return;
+        }
+
+        if (permissionsState.revokeRuntimePermission(bp, userId)
+                == PERMISSION_OPERATION_FAILURE) {
+            return;
+        }
+
+        if (bp.isRuntime()) {
+            logPermission(MetricsEvent.ACTION_PERMISSION_REVOKED, permName, packageName);
+        }
+
+        if (callback != null) {
+            callback.onPermissionRevoked(pkg.applicationInfo.uid, userId);
+        }
+
+        if (bp.isRuntime()) {
+            notifyRuntimePermissionStateChanged(packageName, userId);
+        }
+    }
+
+    @Override
+    public void resetRuntimePermissions() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+                "revokeRuntimePermission");
+
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "resetRuntimePermissions");
+        }
+
+        updateAllPermissions(
+                StorageManager.UUID_PRIVATE_INTERNAL, false, mDefaultPermissionCallback);
+        for (final int userId : UserManagerService.getInstance().getUserIds()) {
+            mPackageManagerInt.forEachPackage(
+                    (PackageParser.Package pkg) -> resetRuntimePermissionsInternal(pkg, userId));
+        }
+    }
+
+    /**
+     * Reverts user permission state changes (permissions and flags).
+     *
+     * @param ps The package for which to reset.
+     * @param userId The device user for which to do a reset.
+     */
+    @GuardedBy("mPackages")
+    private void resetRuntimePermissionsInternal(final PackageParser.Package pkg,
+            final int userId) {
+        final String packageName = pkg.packageName;
+
+        // These are flags that can change base on user actions.
+        final int userSettableMask = FLAG_PERMISSION_USER_SET
+                | FLAG_PERMISSION_USER_FIXED
+                | FLAG_PERMISSION_REVOKE_ON_UPGRADE
+                | FLAG_PERMISSION_REVIEW_REQUIRED;
+
+        final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED
+                | FLAG_PERMISSION_POLICY_FIXED;
+
+        // Delay and combine non-async permission callbacks
+        final int permissionCount = pkg.requestedPermissions.size();
+        final boolean[] permissionRemoved = new boolean[1];
+        final ArraySet<Long> revokedPermissions = new ArraySet<>();
+        final IntArray syncUpdatedUsers = new IntArray(permissionCount);
+        final IntArray asyncUpdatedUsers = new IntArray(permissionCount);
+
+        PermissionCallback delayingPermCallback = new PermissionCallback() {
+            public void onGidsChanged(int appId, int userId) {
+                mDefaultPermissionCallback.onGidsChanged(appId, userId);
+            }
+
+            public void onPermissionChanged() {
+                mDefaultPermissionCallback.onPermissionChanged();
+            }
+
+            public void onPermissionGranted(int uid, int userId) {
+                mDefaultPermissionCallback.onPermissionGranted(uid, userId);
+            }
+
+            public void onInstallPermissionGranted() {
+                mDefaultPermissionCallback.onInstallPermissionGranted();
+            }
+
+            public void onPermissionRevoked(int uid, int userId) {
+                revokedPermissions.add(IntPair.of(uid, userId));
+
+                syncUpdatedUsers.add(userId);
+            }
+
+            public void onInstallPermissionRevoked() {
+                mDefaultPermissionCallback.onInstallPermissionRevoked();
+            }
+
+            public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
+                for (int userId : updatedUserIds) {
+                    if (sync) {
+                        syncUpdatedUsers.add(userId);
+                        asyncUpdatedUsers.remove(userId);
+                    } else {
+                        // Don't override sync=true by sync=false
+                        if (syncUpdatedUsers.indexOf(userId) == -1) {
+                            asyncUpdatedUsers.add(userId);
+                        }
+                    }
+                }
+            }
+
+            public void onPermissionRemoved() {
+                permissionRemoved[0] = true;
+            }
+
+            public void onInstallPermissionUpdated() {
+                mDefaultPermissionCallback.onInstallPermissionUpdated();
+            }
+        };
+
+        for (int i = 0; i < permissionCount; i++) {
+            final String permName = pkg.requestedPermissions.get(i);
+            final BasePermission bp;
+            synchronized (mLock) {
+                bp = mSettings.getPermissionLocked(permName);
+            }
+            if (bp == null) {
+                continue;
+            }
+
+            if (bp.isRemoved()) {
+                continue;
+            }
+
+            // If shared user we just reset the state to which only this app contributed.
+            final String sharedUserId =
+                    mPackageManagerInt.getSharedUserIdForPackage(pkg.packageName);
+            final String[] pkgNames =
+                    mPackageManagerInt.getPackagesForSharedUserId(sharedUserId, userId);
+            if (pkgNames != null && pkgNames.length > 0) {
+                boolean used = false;
+                final int packageCount = pkgNames.length;
+                for (int j = 0; j < packageCount; j++) {
+                    final String sharedPkgName = pkgNames[j];
+                    final PackageParser.Package sharedPkg =
+                            mPackageManagerInt.getPackage(sharedPkgName);
+                    if (sharedPkg != null && !sharedPkg.packageName.equals(packageName)
+                            && sharedPkg.requestedPermissions.contains(permName)) {
+                        used = true;
+                        break;
+                    }
+                }
+                if (used) {
+                    continue;
+                }
+            }
+
+            final int oldFlags =
+                    getPermissionFlagsInternal(permName, packageName, Process.SYSTEM_UID, userId);
+
+            // Always clear the user settable flags.
+            // If permission review is enabled and this is a legacy app, mark the
+            // permission as requiring a review as this is the initial state.
+            final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
+            final int targetSdk = mPackageManagerInt.getTargetSdk(uid);
+            final int flags = (targetSdk < Build.VERSION_CODES.M && bp.isRuntime())
+                    ? FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKE_ON_UPGRADE
+                    : 0;
+
+            updatePermissionFlagsInternal(
+                    permName, packageName, userSettableMask, flags, Process.SYSTEM_UID, userId,
+                    false, delayingPermCallback);
+
+            // Below is only runtime permission handling.
+            if (!bp.isRuntime()) {
+                continue;
+            }
+
+            // Never clobber system or policy.
+            if ((oldFlags & policyOrSystemFlags) != 0) {
+                continue;
+            }
+
+            // If this permission was granted by default, make sure it is.
+            if ((oldFlags & FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0) {
+                grantRuntimePermissionInternal(permName, packageName, false,
+                        Process.SYSTEM_UID, userId, delayingPermCallback);
+            // If permission review is enabled the permissions for a legacy apps
+            // are represented as constantly granted runtime ones, so don't revoke.
+            } else if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
+                // Otherwise, reset the permission.
+                revokeRuntimePermissionInternal(permName, packageName, false, Process.SYSTEM_UID,
+                        userId, delayingPermCallback);
+            }
+        }
+
+        // Execute delayed callbacks
+        if (permissionRemoved[0]) {
+            mDefaultPermissionCallback.onPermissionRemoved();
+        }
+
+        // Slight variation on the code in mPermissionCallback.onPermissionRevoked() as we cannot
+        // kill uid while holding mPackages-lock
+        if (!revokedPermissions.isEmpty()) {
+            int numRevokedPermissions = revokedPermissions.size();
+            for (int i = 0; i < numRevokedPermissions; i++) {
+                int revocationUID = IntPair.first(revokedPermissions.valueAt(i));
+                int revocationUserId = IntPair.second(revokedPermissions.valueAt(i));
+
+                mOnPermissionChangeListeners.onPermissionsChanged(revocationUID);
+
+                // Kill app later as we are holding mPackages
+                mHandler.post(() -> killUid(UserHandle.getAppId(revocationUID), revocationUserId,
+                        KILL_APP_REASON_PERMISSIONS_REVOKED));
+            }
+        }
+
+        mPackageManagerInt.writePermissionSettings(syncUpdatedUsers.toArray(), false);
+        mPackageManagerInt.writePermissionSettings(asyncUpdatedUsers.toArray(), true);
+    }
+
     /**
      * Get the state of the runtime permissions as xml file.
      *
@@ -717,6 +1788,7 @@
             }
         }
 
+        final int callingUid = Binder.getCallingUid();
         final int numNewPackagePermissions = newPackage.permissions.size();
         for (int newPermissionNum = 0; newPermissionNum < numNewPackagePermissions;
                 newPermissionNum++) {
@@ -741,9 +1813,9 @@
                         final int numPackages = allPackageNames.size();
                         for (int packageNum = 0; packageNum < numPackages; packageNum++) {
                             final String packageName = allPackageNames.get(packageNum);
-
-                            if (checkPermission(permissionName, packageName, UserHandle.USER_SYSTEM,
-                                    userId) == PackageManager.PERMISSION_GRANTED) {
+                            final int permissionState = checkPermissionInternal(
+                                    permissionName, packageName, UserHandle.USER_SYSTEM, userId);
+                            if (permissionState == PackageManager.PERMISSION_GRANTED) {
                                 EventLog.writeEvent(0x534e4554, "72710897",
                                         newPackage.applicationInfo.uid,
                                         "Revoking permission " + permissionName +
@@ -752,8 +1824,8 @@
                                         " to " + newPermissionGroupName);
 
                                 try {
-                                    revokeRuntimePermission(permissionName, packageName, false,
-                                            userId, permissionCallback);
+                                    revokeRuntimePermissionInternal(permissionName, packageName,
+                                            false, callingUid, userId, permissionCallback);
                                 } catch (IllegalArgumentException e) {
                                     Slog.e(TAG, "Could not revoke " + permissionName + " from "
                                             + packageName, e);
@@ -2001,7 +3073,7 @@
             }
             for (int userId : mUserManagerInt.getUserIds()) {
                 if (disabledPs.getPermissionsState().hasRuntimePermission(permission, userId)) {
-                    grantRuntimePermission(
+                    grantRuntimePermissionInternal(
                             permission, pkg.packageName, false, callingUid, userId, callback);
                 }
             }
@@ -2016,54 +3088,6 @@
         }
     }
 
-    private @Nullable List<String> getWhitelistedRestrictedPermissions(
-            @NonNull PackageParser.Package pkg, @PermissionWhitelistFlags int whitelistFlags,
-            @UserIdInt int userId) {
-        final PackageSetting packageSetting = (PackageSetting) pkg.mExtras;
-        if (packageSetting == null) {
-            return null;
-        }
-
-        final PermissionsState permissionsState = packageSetting.getPermissionsState();
-
-        int queryFlags = 0;
-        if ((whitelistFlags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0) {
-            queryFlags |= PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
-        }
-        if ((whitelistFlags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) {
-            queryFlags |= PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
-        }
-        if ((whitelistFlags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
-            queryFlags |=  PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-        }
-
-        ArrayList<String> whitelistedPermissions = null;
-
-        final int permissionCount = pkg.requestedPermissions.size();
-        for (int i = 0; i < permissionCount; i++) {
-            final String permissionName = pkg.requestedPermissions.get(i);
-            final int currentFlags = permissionsState.getPermissionFlags(permissionName, userId);
-            if ((currentFlags & queryFlags) != 0) {
-                if (whitelistedPermissions == null) {
-                    whitelistedPermissions = new ArrayList<>();
-                }
-                whitelistedPermissions.add(permissionName);
-            }
-        }
-
-        return whitelistedPermissions;
-    }
-
-    private void setWhitelistedRestrictedPermissions(@NonNull PackageParser.Package pkg,
-            @NonNull int[] userIds, @Nullable List<String> permissions, int callingUid,
-            @PackageManager.PermissionWhitelistFlags int whitelistFlags,
-            @NonNull PermissionCallback callback) {
-        for (int userId : userIds) {
-            setWhitelistedRestrictedPermissionsForUser(pkg, userId, permissions,
-                    callingUid, whitelistFlags, callback);
-        }
-    }
-
     private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId,
             String[] grantedPermissions, int callingUid, PermissionCallback callback) {
         PackageSetting ps = (PackageSetting) pkg.mExtras;
@@ -2095,14 +3119,14 @@
                 if (supportsRuntimePermissions) {
                     // Installer cannot change immutable permissions.
                     if ((flags & immutableFlags) == 0) {
-                        grantRuntimePermission(permission, pkg.packageName, false, callingUid,
-                                userId, callback);
+                        grantRuntimePermissionInternal(permission, pkg.packageName, false,
+                                callingUid, userId, callback);
                     }
                 } else {
                     // In permission review mode we clear the review flag when we
                     // are asked to install the app with all permissions granted.
                     if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
-                        updatePermissionFlags(permission, pkg.packageName,
+                        updatePermissionFlagsInternal(permission, pkg.packageName,
                                 PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0, callingUid,
                                 userId, false, callback);
                     }
@@ -2111,262 +3135,15 @@
         }
     }
 
-    private void grantRuntimePermission(String permName, String packageName, boolean overridePolicy,
-            int callingUid, final int userId, PermissionCallback callback) {
-        if (ApplicationPackageManager.DEBUG_TRACE_GRANTS
-                && ApplicationPackageManager.shouldTraceGrant(packageName, permName, userId)) {
-            Log.i(TAG, "System is granting "
-                    + permName + " for user " + userId + " on behalf of uid " + callingUid
-                    + " " + mPackageManagerInt.getNameForUid(callingUid),
-                    new RuntimeException());
-        }
-        if (!mUserManagerInt.exists(userId)) {
-            Log.e(TAG, "No such user:" + userId);
-            return;
-        }
-
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
-                "grantRuntimePermission");
-
-        enforceCrossUserPermission(callingUid, userId,
-                true,  // requireFullPermission
-                true,  // checkShell
-                false, // requirePermissionWhenSameUser
-                "grantRuntimePermission");
-
-        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
-        if (pkg == null || pkg.mExtras == null) {
-            throw new IllegalArgumentException("Unknown package: " + packageName);
-        }
-        final BasePermission bp;
-        synchronized(mLock) {
-            bp = mSettings.getPermissionLocked(permName);
-        }
-        if (bp == null) {
-            throw new IllegalArgumentException("Unknown permission: " + permName);
-        }
-        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
-            throw new IllegalArgumentException("Unknown package: " + packageName);
-        }
-
-        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg);
-
-        // 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 (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
-                && bp.isRuntime()) {
-            return;
-        }
-
-        final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
-
-        final PackageSetting ps = (PackageSetting) pkg.mExtras;
-        final PermissionsState permissionsState = ps.getPermissionsState();
-
-        final int flags = permissionsState.getPermissionFlags(permName, userId);
-        if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
-            Log.e(TAG, "Cannot grant system fixed permission "
-                    + permName + " for package " + packageName);
-            return;
-        }
-        if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
-            Log.e(TAG, "Cannot grant policy fixed permission "
-                    + permName + " for package " + packageName);
-            return;
-        }
-
-        if (bp.isHardRestricted()
-                && (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) {
-            Log.e(TAG, "Cannot grant hard restricted non-exempt permission "
-                    + permName + " for package " + packageName);
-            return;
-        }
-
-        if (bp.isSoftRestricted() && !SoftRestrictedPermissionPolicy.forPermission(mContext,
-                pkg.applicationInfo, UserHandle.of(userId), permName).canBeGranted()) {
-            Log.e(TAG, "Cannot grant soft restricted permission " + permName + " for package "
-                    + packageName);
-            return;
-        }
-
-        if (bp.isDevelopment()) {
-            // Development permissions must be handled specially, since they are not
-            // normal runtime permissions.  For now they apply to all users.
-            if (permissionsState.grantInstallPermission(bp) !=
-                    PERMISSION_OPERATION_FAILURE) {
-                if (callback != null) {
-                    callback.onInstallPermissionGranted();
-                }
-            }
-            return;
-        }
-
-        if (ps.getInstantApp(userId) && !bp.isInstant()) {
-            throw new SecurityException("Cannot grant non-ephemeral permission"
-                    + permName + " for package " + packageName);
-        }
-
-        if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
-            Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
-            return;
-        }
-
-        final int result = permissionsState.grantRuntimePermission(bp, userId);
-        switch (result) {
-            case PERMISSION_OPERATION_FAILURE: {
-                return;
-            }
-
-            case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
-                if (callback != null) {
-                    callback.onGidsChanged(UserHandle.getAppId(pkg.applicationInfo.uid), userId);
-                }
-            }
-            break;
-        }
-
-        if (bp.isRuntime()) {
-            logPermission(MetricsEvent.ACTION_PERMISSION_GRANTED, permName, packageName);
-        }
-
-        if (callback != null) {
-            callback.onPermissionGranted(uid, userId);
-        }
-
-        if (bp.isRuntime()) {
-            notifyRuntimePermissionStateChanged(packageName, userId);
-        }
-
-        // Only need to do this if user is initialized. Otherwise it's a new user
-        // and there are no processes running as the user yet and there's no need
-        // to make an expensive call to remount processes for the changed permissions.
-        if (READ_EXTERNAL_STORAGE.equals(permName)
-                || WRITE_EXTERNAL_STORAGE.equals(permName)) {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                if (mUserManagerInt.isUserInitialized(userId)) {
-                    StorageManagerInternal storageManagerInternal = LocalServices.getService(
-                            StorageManagerInternal.class);
-                    storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-    }
-
-    private void revokeRuntimePermission(String permName, String packageName,
-            boolean overridePolicy, int userId, PermissionCallback callback) {
-        int callingUid = Binder.getCallingUid();
-        if (ApplicationPackageManager.DEBUG_TRACE_GRANTS
-                && ApplicationPackageManager.shouldTraceGrant(packageName, permName, userId)) {
-            Log.i(TAG, "System is revoking "
-                            + permName + " for user " + userId + " on behalf of uid " + callingUid
-                            + " " + mPackageManagerInt.getNameForUid(callingUid),
-                    new RuntimeException());
-        }
-        if (!mUserManagerInt.exists(userId)) {
-            Log.e(TAG, "No such user:" + userId);
-            return;
-        }
-
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
-                "revokeRuntimePermission");
-
-        enforceCrossUserPermission(callingUid, userId,
-                true,  // requireFullPermission
-                true,  // checkShell
-                false, // requirePermissionWhenSameUser
-                "revokeRuntimePermission");
-
-        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
-        if (pkg == null || pkg.mExtras == null) {
-            throw new IllegalArgumentException("Unknown package: " + packageName);
-        }
-        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
-            throw new IllegalArgumentException("Unknown package: " + packageName);
-        }
-        final BasePermission bp = mSettings.getPermissionLocked(permName);
-        if (bp == null) {
-            throw new IllegalArgumentException("Unknown permission: " + permName);
-        }
-
-        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg);
-
-        // 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 (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
-                && bp.isRuntime()) {
-            return;
-        }
-
-        final PackageSetting ps = (PackageSetting) pkg.mExtras;
-        final PermissionsState permissionsState = ps.getPermissionsState();
-
-        final int flags = permissionsState.getPermissionFlags(permName, userId);
-        // Only the system may revoke SYSTEM_FIXED permissions.
-        if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
-                && UserHandle.getCallingAppId() != Process.SYSTEM_UID) {
-            throw new SecurityException("Non-System UID cannot revoke system fixed permission "
-                    + permName + " for package " + packageName);
-        }
-        if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
-            throw new SecurityException("Cannot revoke policy fixed permission "
-                    + permName + " for package " + packageName);
-        }
-
-        if (bp.isDevelopment()) {
-            // Development permissions must be handled specially, since they are not
-            // normal runtime permissions.  For now they apply to all users.
-            if (permissionsState.revokeInstallPermission(bp) !=
-                    PERMISSION_OPERATION_FAILURE) {
-                if (callback != null) {
-                    callback.onInstallPermissionRevoked();
-                }
-            }
-            return;
-        }
-
-        // Permission is already revoked, no need to do anything.
-        if (!permissionsState.hasRuntimePermission(permName, userId)) {
-            return;
-        }
-
-        if (permissionsState.revokeRuntimePermission(bp, userId) ==
-                PERMISSION_OPERATION_FAILURE) {
-            return;
-        }
-
-        if (bp.isRuntime()) {
-            logPermission(MetricsEvent.ACTION_PERMISSION_REVOKED, permName, packageName);
-        }
-
-        if (callback != null) {
-            callback.onPermissionRevoked(pkg.applicationInfo.uid, userId);
-        }
-
-        if (bp.isRuntime()) {
-            notifyRuntimePermissionStateChanged(packageName, userId);
-        }
-    }
-
     private void setWhitelistedRestrictedPermissionsForUser(@NonNull PackageParser.Package pkg,
             @UserIdInt int userId, @Nullable List<String> permissions, int callingUid,
             @PermissionWhitelistFlags int whitelistFlags, PermissionCallback callback) {
-        final PackageSetting ps = (PackageSetting) pkg.mExtras;
-        if (ps == null) {
+        final PermissionsState permissionsState =
+                PackageManagerServiceUtils.getPermissionsState(pkg);
+        if (permissionsState == null) {
             return;
         }
 
-        final PermissionsState permissionsState = ps.getPermissionsState();
-
         ArraySet<String> oldGrantedRestrictedPermissions = null;
         boolean updatePermissions = false;
 
@@ -2454,7 +3231,7 @@
                 newFlags |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
             }
 
-            updatePermissionFlags(permissionName, pkg.packageName, mask, newFlags,
+            updatePermissionFlagsInternal(permissionName, pkg.packageName, mask, newFlags,
                     callingUid, userId, false, null /*callback*/);
         }
 
@@ -2468,7 +3245,9 @@
                 for (int i = 0; i < oldGrantedCount; i++) {
                     final String permission = oldGrantedRestrictedPermissions.valueAt(i);
                     // Sometimes we create a new permission state instance during update.
-                    if (!ps.getPermissionsState().hasPermission(permission, userId)) {
+                    final PermissionsState newPermissionsState =
+                            PackageManagerServiceUtils.getPermissionsState(pkg);
+                    if (!newPermissionsState.hasPermission(permission, userId)) {
                         callback.onPermissionRevoked(pkg.applicationInfo.uid, userId);
                         break;
                     }
@@ -2541,37 +3320,6 @@
         return runtimePermissionChangedUserIds;
     }
 
-    private int getPermissionFlags(
-            String permName, String packageName, int callingUid, int userId) {
-        if (!mUserManagerInt.exists(userId)) {
-            return 0;
-        }
-
-        enforceGrantRevokeGetRuntimePermissionPermissions("getPermissionFlags");
-
-        enforceCrossUserPermission(callingUid, userId,
-                true,  // requireFullPermission
-                false, // checkShell
-                false, // requirePermissionWhenSameUser
-                "getPermissionFlags");
-
-        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
-        if (pkg == null || pkg.mExtras == null) {
-            return 0;
-        }
-        synchronized (mLock) {
-            if (mSettings.getPermissionLocked(permName) == null) {
-                return 0;
-            }
-        }
-        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
-            return 0;
-        }
-        final PackageSetting ps = (PackageSetting) pkg.mExtras;
-        PermissionsState permissionsState = ps.getPermissionsState();
-        return permissionsState.getPermissionFlags(permName, userId);
-    }
-
     /**
      * Update permissions when a package changed.
      *
@@ -2586,16 +3334,15 @@
      * @param callback Callback to call after permission changes
      */
     private void updatePermissions(@NonNull String packageName, @Nullable PackageParser.Package pkg,
-            @NonNull Collection<PackageParser.Package> allPackages,
             @NonNull PermissionCallback callback) {
         final int flags =
                 (pkg != null ? UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG : 0);
         updatePermissions(
-                packageName, pkg, getVolumeUuidForPackage(pkg), flags, allPackages, callback);
+                packageName, pkg, getVolumeUuidForPackage(pkg), flags, callback);
         if (pkg != null && pkg.childPackages != null) {
             for (PackageParser.Package childPkg : pkg.childPackages) {
                 updatePermissions(childPkg.packageName, childPkg,
-                        getVolumeUuidForPackage(childPkg), flags, allPackages, callback);
+                        getVolumeUuidForPackage(childPkg), flags, callback);
             }
         }
     }
@@ -2613,13 +3360,12 @@
      * @param callback Callback to call after permission changes
      */
     private void updateAllPermissions(@Nullable String volumeUuid, boolean sdkUpdated,
-            @NonNull Collection<PackageParser.Package> allPackages,
             @NonNull 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);
+        updatePermissions(null, null, volumeUuid, flags, callback);
     }
 
     /**
@@ -2697,11 +3443,11 @@
      * @param allPackages All currently known packages
      * @param callback Callback to call after permission changes
      */
-    private void updatePermissions(@Nullable String changingPkgName,
-            @Nullable PackageParser.Package changingPkg, @Nullable String replaceVolumeUuid,
+    private void updatePermissions(final @Nullable String changingPkgName,
+            final @Nullable PackageParser.Package changingPkg,
+            final @Nullable String replaceVolumeUuid,
             @UpdatePermissionFlags int flags,
-            @NonNull Collection<PackageParser.Package> allPackages,
-            @Nullable PermissionCallback callback) {
+            final @Nullable 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
@@ -2730,15 +3476,16 @@
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "restorePermissionState");
         // Now update the permissions for all 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);
-                    restorePermissionState(pkg, replace, changingPkgName, callback);
+            final boolean replaceAll = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0);
+            mPackageManagerInt.forEachPackage((Package pkg) -> {
+                if (pkg == changingPkg) {
+                    return;
                 }
-            }
+                // Only replace for packages on requested volume
+                final String volumeUuid = getVolumeUuidForPackage(pkg);
+                final boolean replace = replaceAll && Objects.equals(replaceVolumeUuid, volumeUuid);
+                restorePermissionState(pkg, replace, changingPkgName, callback);
+            });
         }
 
         if (changingPkg != null) {
@@ -2880,127 +3627,6 @@
         return changed;
     }
 
-    private void updatePermissionFlags(String permName, String packageName, int flagMask,
-            int flagValues, int callingUid, int userId, boolean overridePolicy,
-            PermissionCallback callback) {
-        if (ApplicationPackageManager.DEBUG_TRACE_GRANTS
-                && ApplicationPackageManager.shouldTraceGrant(packageName, permName, userId)) {
-            Log.i(TAG, "System is updating flags for "
-                            + permName + " for user " + userId  + " "
-                            + DebugUtils.flagsToString(
-                                    PackageManager.class, "FLAG_PERMISSION_", flagMask)
-                            + " := "
-                            + DebugUtils.flagsToString(
-                                    PackageManager.class, "FLAG_PERMISSION_", flagValues)
-                            + " on behalf of uid " + callingUid
-                            + " " + mPackageManagerInt.getNameForUid(callingUid),
-                    new RuntimeException());
-        }
-
-        if (!mUserManagerInt.exists(userId)) {
-            return;
-        }
-
-        enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags");
-
-        enforceCrossUserPermission(callingUid, userId,
-                true,  // requireFullPermission
-                true,  // checkShell
-                false, // requirePermissionWhenSameUser
-                "updatePermissionFlags");
-
-        if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0 && !overridePolicy) {
-            throw new SecurityException("updatePermissionFlags requires "
-                    + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
-        }
-
-        // Only the system can change these flags and nothing else.
-        if (callingUid != Process.SYSTEM_UID) {
-            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-            flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
-        }
-
-        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
-        if (pkg == null || pkg.mExtras == null) {
-            Log.e(TAG, "Unknown package: " + packageName);
-            return;
-        }
-        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
-            throw new IllegalArgumentException("Unknown package: " + packageName);
-        }
-
-        final BasePermission bp;
-        synchronized (mLock) {
-            bp = mSettings.getPermissionLocked(permName);
-        }
-        if (bp == null) {
-            throw new IllegalArgumentException("Unknown permission: " + permName);
-        }
-
-        final PackageSetting ps = (PackageSetting) pkg.mExtras;
-        final PermissionsState permissionsState = ps.getPermissionsState();
-        final boolean hadState =
-                permissionsState.getRuntimePermissionState(permName, userId) != null;
-        final boolean permissionUpdated =
-                permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues);
-        if (permissionUpdated && bp.isRuntime()) {
-            notifyRuntimePermissionStateChanged(packageName, userId);
-        }
-        if (permissionUpdated && callback != null) {
-            // Install and runtime permissions are stored in different places,
-            // so figure out what permission changed and persist the change.
-            if (permissionsState.getInstallPermissionState(permName) != null) {
-                callback.onInstallPermissionUpdatedNotifyListener(pkg.applicationInfo.uid);
-            } else if (permissionsState.getRuntimePermissionState(permName, userId) != null
-                    || hadState) {
-                callback.onPermissionUpdatedNotifyListener(new int[] { userId }, false,
-                        pkg.applicationInfo.uid);
-            }
-        }
-    }
-
-    private boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues, int callingUid,
-            int userId, Collection<Package> packages, PermissionCallback callback) {
-        if (!mUserManagerInt.exists(userId)) {
-            return false;
-        }
-
-        enforceGrantRevokeRuntimePermissionPermissions(
-                "updatePermissionFlagsForAllApps");
-        enforceCrossUserPermission(callingUid, userId,
-                true,  // requireFullPermission
-                true,  // checkShell
-                false, // requirePermissionWhenSameUser
-                "updatePermissionFlagsForAllApps");
-
-        // Only the system can change system fixed flags.
-        if (callingUid != Process.SYSTEM_UID) {
-            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-        }
-
-        boolean changed = false;
-        for (PackageParser.Package pkg : packages) {
-            final PackageSetting ps = (PackageSetting) pkg.mExtras;
-            if (ps == null) {
-                continue;
-            }
-            PermissionsState permissionsState = ps.getPermissionsState();
-            changed |= permissionsState.updatePermissionFlagsForAllPermissions(
-                    userId, flagMask, flagValues);
-            callback.onPermissionUpdatedNotifyListener(new int[] { userId }, false,
-                    pkg.applicationInfo.uid);
-        }
-        return changed;
-    }
-
     private void enforceGrantRevokeRuntimePermissionPermissions(String message) {
         if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
                 != PackageManager.PERMISSION_GRANTED
@@ -3153,10 +3779,9 @@
         public void revokeRuntimePermissionsIfGroupChanged(
                 @NonNull PackageParser.Package newPackage,
                 @NonNull PackageParser.Package oldPackage,
-                @NonNull ArrayList<String> allPackageNames,
-                @NonNull PermissionCallback permissionCallback) {
+                @NonNull ArrayList<String> allPackageNames) {
             PermissionManagerService.this.revokeRuntimePermissionsIfGroupChanged(newPackage,
-                    oldPackage, allPackageNames, permissionCallback);
+                    oldPackage, allPackageNames, mDefaultPermissionCallback);
         }
         @Override
         public void addAllPermissions(Package pkg, boolean chatty) {
@@ -3171,57 +3796,50 @@
             PermissionManagerService.this.removeAllPermissions(pkg, chatty);
         }
         @Override
-        public void grantRuntimePermission(String permName, String packageName,
-                boolean overridePolicy, int callingUid, int userId,
-                PermissionCallback callback) {
-            PermissionManagerService.this.grantRuntimePermission(
-                    permName, packageName, overridePolicy, callingUid, userId, callback);
-        }
-        @Override
         public void grantRequestedRuntimePermissions(PackageParser.Package pkg, int[] userIds,
-                String[] grantedPermissions, int callingUid, PermissionCallback callback) {
+                String[] grantedPermissions, int callingUid) {
             PermissionManagerService.this.grantRequestedRuntimePermissions(
-                    pkg, userIds, grantedPermissions, callingUid, callback);
-        }
-        @Override
-        public List<String> getWhitelistedRestrictedPermissions(PackageParser.Package pkg,
-                @PackageManager.PermissionWhitelistFlags int whitelistFlags, int userId) {
-            return PermissionManagerService.this.getWhitelistedRestrictedPermissions(pkg,
-                    whitelistFlags, userId);
+                    pkg, userIds, grantedPermissions, callingUid, mDefaultPermissionCallback);
         }
         @Override
         public void setWhitelistedRestrictedPermissions(@NonNull PackageParser.Package pkg,
                 @NonNull int[] userIds, @Nullable List<String> permissions, int callingUid,
-                @PackageManager.PermissionWhitelistFlags int whitelistFlags,
-                @NonNull PermissionCallback callback) {
-            PermissionManagerService.this.setWhitelistedRestrictedPermissions(
-                    pkg, userIds, permissions, callingUid, whitelistFlags, callback);
+                @PackageManager.PermissionWhitelistFlags int flags) {
+            for (int userId : userIds) {
+                setWhitelistedRestrictedPermissionsForUser(pkg, userId, permissions,
+                        callingUid, flags, mDefaultPermissionCallback);
+            }
+        }
+        @Override
+        public void setWhitelistedRestrictedPermissions(String packageName,
+                List<String> permissions, int flags, int userId) {
+            PermissionManagerService.this.setWhitelistedRestrictedPermissionsInternal(
+                    packageName, permissions, flags, userId);
         }
         @Override
         public void grantRuntimePermissionsGrantedToDisabledPackage(PackageParser.Package pkg,
-                int callingUid, PermissionCallback callback) {
+                int callingUid) {
             PermissionManagerService.this.grantRuntimePermissionsGrantedToDisabledPackageLocked(
-                    pkg, callingUid, callback);
+                    pkg, callingUid, mDefaultPermissionCallback);
         }
         @Override
-        public void revokeRuntimePermission(String permName, String packageName,
-                boolean overridePolicy, int userId, PermissionCallback callback) {
-            PermissionManagerService.this.revokeRuntimePermission(permName, packageName,
-                    overridePolicy, userId, callback);
+        public void updatePermissions(@NonNull String packageName, @Nullable Package pkg) {
+            PermissionManagerService.this
+                    .updatePermissions(packageName, pkg, mDefaultPermissionCallback);
         }
         @Override
-        public void updatePermissions(@NonNull String packageName, @Nullable Package pkg,
-                @NonNull Collection<PackageParser.Package> allPackages,
-                @NonNull PermissionCallback callback) {
-            PermissionManagerService.this.updatePermissions(
-                    packageName, pkg, allPackages, callback);
+        public void updateAllPermissions(@Nullable String volumeUuid, boolean sdkUpdated) {
+            PermissionManagerService.this
+                    .updateAllPermissions(volumeUuid, sdkUpdated, mDefaultPermissionCallback);
         }
         @Override
-        public void updateAllPermissions(@Nullable String volumeUuid, boolean sdkUpdated,
-                @NonNull Collection<PackageParser.Package> allPackages,
-                @NonNull PermissionCallback callback) {
-            PermissionManagerService.this.updateAllPermissions(
-                    volumeUuid, sdkUpdated, allPackages, callback);
+        public void resetRuntimePermissions(Package pkg, int userId) {
+            PermissionManagerService.this.resetRuntimePermissionsInternal(pkg, userId);
+        }
+        @Override
+        public void resetAllRuntimePermissions(final int userId) {
+            mPackageManagerInt.forEachPackage(
+                    (PackageParser.Package pkg) -> resetRuntimePermissionsInternal(pkg, userId));
         }
         @Override
         public String[] getAppOpPermissionPackages(String permName, int callingUid) {
@@ -3231,24 +3849,18 @@
         @Override
         public int getPermissionFlags(String permName, String packageName, int callingUid,
                 int userId) {
-            return PermissionManagerService.this.getPermissionFlags(permName, packageName,
-                    callingUid, userId);
+            return PermissionManagerService.this
+                    .getPermissionFlagsInternal(permName, packageName, callingUid, userId);
         }
         @Override
         public void updatePermissionFlags(String permName, String packageName, int flagMask,
                 int flagValues, int callingUid, int userId, boolean overridePolicy,
                 PermissionCallback callback) {
-            PermissionManagerService.this.updatePermissionFlags(
+            PermissionManagerService.this.updatePermissionFlagsInternal(
                     permName, packageName, flagMask, flagValues, callingUid, userId,
                     overridePolicy, callback);
         }
         @Override
-        public boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues, int callingUid,
-                int userId, Collection<Package> packages, PermissionCallback callback) {
-            return PermissionManagerService.this.updatePermissionFlagsForAllApps(
-                    flagMask, flagValues, callingUid, userId, packages, callback);
-        }
-        @Override
         public void enforceCrossUserPermission(int callingUid, int userId,
                 boolean requireFullPermission, boolean checkShell, String message) {
             PermissionManagerService.this.enforceCrossUserPermission(callingUid, userId,
@@ -3266,17 +3878,6 @@
             PermissionManagerService.this.enforceGrantRevokeRuntimePermissionPermissions(message);
         }
         @Override
-        public int checkPermission(String permName, String packageName, int callingUid,
-                int userId) {
-            return PermissionManagerService.this.checkPermission(
-                    permName, packageName, callingUid, userId);
-        }
-        @Override
-        public int checkUidPermission(String permName, PackageParser.Package pkg, int uid,
-                int callingUid) {
-            return PermissionManagerService.this.checkUidPermission(permName, pkg, uid, callingUid);
-        }
-        @Override
         public PermissionSettings getPermissionSettings() {
             return mSettings;
         }
@@ -3341,5 +3942,76 @@
             PermissionManagerService.this.removeOnRuntimePermissionStateChangedListener(
                     listener);
         }
+
+        @Override
+        public CheckPermissionDelegate getCheckPermissionDelegate() {
+            synchronized (mLock) {
+                return mCheckPermissionDelegate;
+            }
+        }
+
+        @Override
+        public void setCheckPermissionDelegate(CheckPermissionDelegate delegate) {
+            synchronized (mLock) {
+                mCheckPermissionDelegate = delegate;
+            }
+        }
+        @Override
+        public void notifyPermissionsChangedTEMP(int uid) {
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+        }
+    }
+
+    private static final class OnPermissionChangeListeners extends Handler {
+        private static final int MSG_ON_PERMISSIONS_CHANGED = 1;
+
+        private final RemoteCallbackList<IOnPermissionsChangeListener> mPermissionListeners =
+                new RemoteCallbackList<>();
+
+        OnPermissionChangeListeners(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ON_PERMISSIONS_CHANGED: {
+                    final int uid = msg.arg1;
+                    handleOnPermissionsChanged(uid);
+                } break;
+            }
+        }
+
+        public void addListenerLocked(IOnPermissionsChangeListener listener) {
+            mPermissionListeners.register(listener);
+
+        }
+
+        public void removeListenerLocked(IOnPermissionsChangeListener listener) {
+            mPermissionListeners.unregister(listener);
+        }
+
+        public void onPermissionsChanged(int uid) {
+            if (mPermissionListeners.getRegisteredCallbackCount() > 0) {
+                obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0).sendToTarget();
+            }
+        }
+
+        private void handleOnPermissionsChanged(int uid) {
+            final int count = mPermissionListeners.beginBroadcast();
+            try {
+                for (int i = 0; i < count; i++) {
+                    IOnPermissionsChangeListener callback = mPermissionListeners
+                            .getBroadcastItem(i);
+                    try {
+                        callback.onPermissionsChanged(uid);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Permission listener is dead", e);
+                    }
+                }
+            } finally {
+                mPermissionListeners.finishBroadcast();
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index a785575..2fdab4d 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -26,7 +26,6 @@
 import android.permission.PermissionManagerInternal;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 
 /**
@@ -73,27 +72,19 @@
     public abstract boolean isPermissionsReviewRequired(@NonNull PackageParser.Package pkg,
             @UserIdInt int userId);
 
-    public abstract void grantRuntimePermission(
-            @NonNull String permName, @NonNull String packageName, boolean overridePolicy,
-            int callingUid, int userId, @Nullable PermissionCallback callback);
     public abstract void grantRuntimePermissionsGrantedToDisabledPackage(
-            @NonNull PackageParser.Package pkg, int callingUid,
-            @Nullable PermissionCallback callback);
+            @NonNull PackageParser.Package pkg, int callingUid);
     public abstract void grantRequestedRuntimePermissions(
             @NonNull PackageParser.Package pkg, @NonNull int[] userIds,
-            @NonNull String[] grantedPermissions, int callingUid,
-            @Nullable PermissionCallback callback);
-    public abstract @Nullable List<String> getWhitelistedRestrictedPermissions(
-            @NonNull PackageParser.Package pkg,
-            @PackageManager.PermissionWhitelistFlags int whitelistFlags, int userId);
+            @NonNull String[] grantedPermissions, int callingUid);
     public abstract void setWhitelistedRestrictedPermissions(
             @NonNull PackageParser.Package pkg, @NonNull int[] userIds,
             @NonNull List<String> permissions, int callingUid,
-            @PackageManager.PermissionWhitelistFlags int whitelistFlags,
-            @Nullable PermissionCallback callback);
-    public abstract void revokeRuntimePermission(@NonNull String permName,
-            @NonNull String packageName, boolean overridePolicy, int userId,
-            @Nullable PermissionCallback callback);
+            @PackageManager.PermissionWhitelistFlags int whitelistFlags);
+    /** Sets the whitelisted, restricted permissions for the given package. */
+    public abstract void setWhitelistedRestrictedPermissions(
+            @NonNull String packageName, @NonNull List<String> permissions,
+            @PackageManager.PermissionWhitelistFlags int flags, @NonNull int userId);
 
     /**
      * Update permissions when a package changed.
@@ -109,9 +100,7 @@
      * @param callback Callback to call after permission changes
      */
     public abstract void updatePermissions(@NonNull String packageName,
-            @Nullable PackageParser.Package pkg,
-            @NonNull Collection<PackageParser.Package> allPackages,
-            @NonNull PermissionCallback callback);
+            @Nullable PackageParser.Package pkg);
 
     /**
      * Update all permissions for all apps.
@@ -125,9 +114,22 @@
      * @param allPackages All currently known packages
      * @param callback Callback to call after permission changes
      */
-    public abstract void updateAllPermissions(@Nullable String volumeUuid, boolean sdkUpdate,
-            @NonNull Collection<PackageParser.Package> allPackages,
-            @NonNull PermissionCallback callback);
+    public abstract void updateAllPermissions(@Nullable String volumeUuid, boolean sdkUpdate);
+
+    /**
+     * Resets any user permission state changes (eg. permissions and flags) of all
+     * packages installed for the given user.
+     *
+     * @see #resetRuntimePermissions(android.content.pm.PackageParser.Package, int)
+     */
+    public abstract void resetAllRuntimePermissions(@UserIdInt int userId);
+
+    /**
+     * Resets any user permission state changes (eg. permissions and flags) of the
+     * specified package for the given user.
+     */
+    public abstract void resetRuntimePermissions(@NonNull PackageParser.Package pkg,
+            @UserIdInt int userId);
 
     /**
      * We might auto-grant permissions if any permission of the group is already granted. Hence if
@@ -137,13 +139,11 @@
      * @param newPackage The new package that was installed
      * @param oldPackage The old package that was updated
      * @param allPackageNames All packages
-     * @param permissionCallback Callback for permission changed
      */
     public abstract void revokeRuntimePermissionsIfGroupChanged(
             @NonNull PackageParser.Package newPackage,
             @NonNull PackageParser.Package oldPackage,
-            @NonNull ArrayList<String> allPackageNames,
-            @NonNull PermissionCallback permissionCallback);
+            @NonNull ArrayList<String> allPackageNames);
 
     /**
      * Add all permissions in the given package.
@@ -169,18 +169,6 @@
     public abstract void updatePermissionFlags(@NonNull String permName,
             @NonNull String packageName, int flagMask, int flagValues, int callingUid, int userId,
             boolean overridePolicy, @Nullable PermissionCallback callback);
-    /**
-     * Updates the flags for all applications by replacing the flags in the specified mask
-     * with the provided flag values.
-     */
-    public abstract boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues,
-            int callingUid, int userId, @NonNull Collection<PackageParser.Package> packages,
-            @Nullable PermissionCallback callback);
-
-    public abstract int checkPermission(@NonNull String permName, @NonNull String packageName,
-            int callingUid, int userId);
-    public abstract int checkUidPermission(@NonNull String permName,
-            @Nullable PackageParser.Package pkg, int uid, int callingUid);
 
     /**
      * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS
@@ -205,8 +193,25 @@
 
     /** HACK HACK methods to allow for partial migration of data to the PermissionManager class */
     public abstract @Nullable BasePermission getPermissionTEMP(@NonNull String permName);
+    /** HACK HACK notify the permission listener; this shouldn't be needed after permissions
+     *  are fully removed from the package manager */
+    public abstract void notifyPermissionsChangedTEMP(int uid);
 
     /** Get all permission that have a certain protection level */
     public abstract @NonNull ArrayList<PermissionInfo> getAllPermissionWithProtectionLevel(
             @PermissionInfo.Protection int protectionLevel);
+
+    /**
+     * Returns the delegate used to influence permission checking.
+     *
+     * @return The delegate instance.
+     */
+    public abstract @Nullable CheckPermissionDelegate getCheckPermissionDelegate();
+
+    /**
+     * Sets the delegate used to influence permission checking.
+     *
+     * @param delegate A delegate instance or {@code null} to clear.
+     */
+    public abstract void setCheckPermissionDelegate(@Nullable CheckPermissionDelegate delegate);
 }
diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
index 3f9eb2d..ee7a098 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -14,6 +14,9 @@
                 },
                 {
                     "include-filter": "android.permission.cts.SharedUidPermissionsTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.PermissionUpdateListenerTest"
                 }
             ]
         },
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 037293f..a569bff 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -798,7 +798,7 @@
         @Override
         public boolean checkStartActivity(@NonNull Intent intent, int callingUid,
                 @Nullable String callingPackage) {
-            if (callingPackage != null && isActionRemovedForCallingPackage(intent.getAction(),
+            if (callingPackage != null && isActionRemovedForCallingPackage(intent, callingUid,
                     callingPackage)) {
                 Slog.w(LOG_TAG, "Action Removed: starting " + intent.toString() + " from "
                         + callingPackage + " (uid=" + callingUid + ")");
@@ -811,8 +811,9 @@
          * Check if the intent action is removed for the calling package (often based on target SDK
          * version). If the action is removed, we'll silently cancel the activity launch.
          */
-        private boolean isActionRemovedForCallingPackage(@Nullable String action,
+        private boolean isActionRemovedForCallingPackage(@NonNull Intent intent, int callingUid,
                 @NonNull String callingPackage) {
+            String action = intent.getAction();
             if (action == null) {
                 return false;
             }
@@ -821,15 +822,19 @@
                 case Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT: {
                     ApplicationInfo applicationInfo;
                     try {
-                        applicationInfo = getContext().getPackageManager().getApplicationInfo(
-                                callingPackage, 0);
+                        applicationInfo = getContext().getPackageManager().getApplicationInfoAsUser(
+                                callingPackage, 0, UserHandle.getUserId(callingUid));
+                        if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q) {
+                            // Applications targeting Q or higher should use
+                            // RoleManager.createRequestRoleIntent() instead.
+                            return true;
+                        }
                     } catch (PackageManager.NameNotFoundException e) {
                         Slog.i(LOG_TAG, "Cannot find application info for " + callingPackage);
-                        return false;
                     }
-                    // Applications targeting Q should use RoleManager.createRequestRoleIntent()
-                    // instead.
-                    return applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q;
+                    // Make sure RequestRoleActivity can know the calling package if we allow it.
+                    intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage);
+                    return false;
                 }
                 default:
                     return false;
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index c6a1867..b503ce8 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.role;
 
 import android.Manifest;
+import android.annotation.AnyThread;
 import android.annotation.CheckResult;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
@@ -49,7 +50,6 @@
 import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
-import android.provider.Telephony;
 import android.service.sms.FinancialSmsService;
 import android.telephony.IFinancialSmsCallback;
 import android.text.TextUtils;
@@ -61,6 +61,8 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ThrottledRunnable;
 import com.android.internal.telephony.SmsApplication;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.BitUtils;
@@ -82,7 +84,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -99,6 +100,8 @@
 
     private static final boolean DEBUG = false;
 
+    private static final long GRANT_DEFAULT_ROLES_INTERVAL_MILLIS = 1000;
+
     @NonNull
     private final UserManagerInternal mUserManagerInternal;
     @NonNull
@@ -142,6 +145,14 @@
     @NonNull
     private final Handler mListenerHandler = FgThread.getHandler();
 
+    /**
+     * Maps user id to its throttled runnable for granting default roles.
+     */
+    @GuardedBy("mLock")
+    @NonNull
+    private final SparseArray<ThrottledRunnable> mGrantDefaultRolesThrottledRunnables =
+            new SparseArray<>();
+
     public RoleManagerService(@NonNull Context context,
             @NonNull RoleHoldersResolver legacyRoleResolver) {
         super(context);
@@ -182,8 +193,6 @@
     public void onStart() {
         publishBinderService(Context.ROLE_SERVICE, new Stub());
 
-        //TODO add watch for new user creation and run default grants for them
-
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
@@ -203,64 +212,78 @@
                     // Package is being upgraded - we're about to get ACTION_PACKAGE_ADDED
                     return;
                 }
-                performInitialGrantsIfNecessaryAsync(userId);
+                maybeGrantDefaultRolesAsync(userId);
             }
         }, UserHandle.ALL, intentFilter, null, null);
     }
 
     @Override
     public void onStartUser(@UserIdInt int userId) {
-        performInitialGrantsIfNecessary(userId);
-    }
-
-    private CompletableFuture<Void> performInitialGrantsIfNecessaryAsync(@UserIdInt int userId) {
-        RoleUserState userState;
-        userState = getOrCreateUserState(userId);
-
-        String packagesHash = computeComponentStateHash(userId);
-        String oldPackagesHash = userState.getPackagesHash();
-        boolean needGrant = !Objects.equals(packagesHash, oldPackagesHash);
-        if (needGrant) {
-
-            //TODO gradually add more role migrations statements here for remaining roles
-            // Make sure to implement LegacyRoleResolutionPolicy#getRoleHolders
-            // for a given role before adding a migration statement for it here
-            migrateRoleIfNecessary(RoleManager.ROLE_SMS, userId);
-            migrateRoleIfNecessary(RoleManager.ROLE_ASSISTANT, userId);
-            migrateRoleIfNecessary(RoleManager.ROLE_DIALER, userId);
-            migrateRoleIfNecessary(RoleManager.ROLE_EMERGENCY, userId);
-
-            // Some vital packages state has changed since last role grant
-            // Run grants again
-            Slog.i(LOG_TAG, "Granting default permissions...");
-            CompletableFuture<Void> result = new CompletableFuture<>();
-            getOrCreateController(userId).grantDefaultRoles(FgThread.getExecutor(),
-                    successful -> {
-                        if (successful) {
-                            userState.setPackagesHash(packagesHash);
-                            result.complete(null);
-                        } else {
-                            result.completeExceptionally(new RuntimeException());
-                        }
-                    });
-            return result;
-        } else if (DEBUG) {
-            Slog.i(LOG_TAG, "Already ran grants for package state " + packagesHash);
-        }
-        return CompletableFuture.completedFuture(null);
+        maybeGrantDefaultRolesSync(userId);
     }
 
     @MainThread
-    private void performInitialGrantsIfNecessary(@UserIdInt int userId) {
-        CompletableFuture<Void> result = performInitialGrantsIfNecessaryAsync(userId);
+    private void maybeGrantDefaultRolesSync(@UserIdInt int userId) {
+        AndroidFuture<Void> future = maybeGrantDefaultRolesInternal(userId);
         try {
-            result.get(30, TimeUnit.SECONDS);
+            future.get(30, TimeUnit.SECONDS);
         } catch (InterruptedException | ExecutionException | TimeoutException e) {
-            Slog.e(LOG_TAG, "Failed to grant defaults for user " + userId, e);
+            Slog.e(LOG_TAG, "Failed to grant default roles for user " + userId, e);
         }
     }
 
-    private void migrateRoleIfNecessary(String role, @UserIdInt int userId) {
+    private void maybeGrantDefaultRolesAsync(@UserIdInt int userId) {
+        ThrottledRunnable runnable;
+        synchronized (mLock) {
+            runnable = mGrantDefaultRolesThrottledRunnables.get(userId);
+            if (runnable == null) {
+                runnable = new ThrottledRunnable(FgThread.getHandler(),
+                        GRANT_DEFAULT_ROLES_INTERVAL_MILLIS,
+                        () -> maybeGrantDefaultRolesInternal(userId));
+                mGrantDefaultRolesThrottledRunnables.put(userId, runnable);
+            }
+        }
+        runnable.run();
+    }
+
+    @AnyThread
+    @NonNull
+    private AndroidFuture<Void> maybeGrantDefaultRolesInternal(@UserIdInt int userId) {
+        RoleUserState userState = getOrCreateUserState(userId);
+        String oldPackagesHash = userState.getPackagesHash();
+        String newPackagesHash = computeComponentStateHash(userId);
+        if (Objects.equals(oldPackagesHash, newPackagesHash)) {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "Already granted default roles for packages hash "
+                        + newPackagesHash);
+            }
+            return AndroidFuture.completedFuture(null);
+        }
+
+        //TODO gradually add more role migrations statements here for remaining roles
+        // Make sure to implement LegacyRoleResolutionPolicy#getRoleHolders
+        // for a given role before adding a migration statement for it here
+        maybeMigrateRole(RoleManager.ROLE_SMS, userId);
+        maybeMigrateRole(RoleManager.ROLE_ASSISTANT, userId);
+        maybeMigrateRole(RoleManager.ROLE_DIALER, userId);
+        maybeMigrateRole(RoleManager.ROLE_EMERGENCY, userId);
+
+        // Some package state has changed, so grant default roles again.
+        Slog.i(LOG_TAG, "Granting default roles...");
+        AndroidFuture<Void> future = new AndroidFuture<>();
+        getOrCreateController(userId).grantDefaultRoles(FgThread.getExecutor(),
+                successful -> {
+                    if (successful) {
+                        userState.setPackagesHash(newPackagesHash);
+                        future.complete(null);
+                    } else {
+                        future.completeExceptionally(new RuntimeException());
+                    }
+                });
+        return future;
+    }
+
+    private void maybeMigrateRole(String role, @UserIdInt int userId) {
         // Any role for which we have a record are already migrated
         RoleUserState userState = getOrCreateUserState(userId);
         if (!userState.isRoleAvailable(role)) {
@@ -366,6 +389,7 @@
         RemoteCallbackList<IOnRoleHoldersChangedListener> listeners;
         RoleUserState userState;
         synchronized (mLock) {
+            mGrantDefaultRolesThrottledRunnables.remove(userId);
             listeners = mListeners.removeReturnOld(userId);
             mControllers.remove(userId);
             userState = mUserStates.removeReturnOld(userId);
@@ -742,7 +766,7 @@
 
         @Override
         public boolean setDefaultBrowser(@Nullable String packageName, @UserIdInt int userId) {
-            CompletableFuture<Void> future = new CompletableFuture<>();
+            AndroidFuture<Void> future = new AndroidFuture<>();
             RemoteCallback callback = new RemoteCallback(result -> {
                 boolean successful = result != null;
                 if (successful) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index fb3076b..1c8c46c 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -826,18 +826,19 @@
         }
     }
 
-    uint32_t changes = 0;
+    bool pointerGesturesEnabledChanged = false;
     { // acquire lock
         AutoMutex _l(mLock);
 
         if (mLocked.pointerGesturesEnabled != newPointerGesturesEnabled) {
             mLocked.pointerGesturesEnabled = newPointerGesturesEnabled;
-            changes |= InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT;
+            pointerGesturesEnabledChanged = true;
         }
     } // release lock
 
-    if (changes) {
-        mInputManager->getReader()->requestRefreshConfiguration(changes);
+    if (pointerGesturesEnabledChanged) {
+        mInputManager->getReader()->requestRefreshConfiguration(
+                InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT);
     }
 }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index de8612a..68c9bad 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -196,6 +196,7 @@
 import android.os.UserManagerInternal;
 import android.os.UserManagerInternal.UserRestrictionsListener;
 import android.os.storage.StorageManager;
+import android.permission.IPermissionManager;
 import android.permission.PermissionControllerManager;
 import android.provider.CalendarContract;
 import android.provider.ContactsContract.QuickContact;
@@ -492,6 +493,7 @@
     final Context mContext;
     final Injector mInjector;
     final IPackageManager mIPackageManager;
+    final IPermissionManager mIPermissionManager;
     final UserManager mUserManager;
     final UserManagerInternal mUserManagerInternal;
     final UsageStatsManagerInternal mUsageStatsManagerInternal;
@@ -1974,6 +1976,10 @@
             return AppGlobals.getPackageManager();
         }
 
+        IPermissionManager getIPermissionManager() {
+            return AppGlobals.getPermissionManager();
+        }
+
         IBackupManager getIBackupManager() {
             return IBackupManager.Stub.asInterface(
                     ServiceManager.getService(Context.BACKUP_SERVICE));
@@ -2217,6 +2223,7 @@
         mUsageStatsManagerInternal = Preconditions.checkNotNull(
                 injector.getUsageStatsManagerInternal());
         mIPackageManager = Preconditions.checkNotNull(injector.getIPackageManager());
+        mIPermissionManager = Preconditions.checkNotNull(injector.getIPermissionManager());
         mTelephonyManager = Preconditions.checkNotNull(injector.getTelephonyManager());
 
         mLocalService = new LocalService();
@@ -8217,7 +8224,7 @@
         saveSettingsLocked(userId);
 
         try {
-            mIPackageManager.updatePermissionFlagsForAllApps(
+            mIPermissionManager.updatePermissionFlagsForAllApps(
                     PackageManager.FLAG_PERMISSION_POLICY_FIXED,
                     0  /* flagValues */, userId);
             pushUserRestrictions(userId);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 2ce4c54..ce83df7 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -37,6 +37,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
+import android.permission.IPermissionManager;
 import android.security.KeyChain;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
@@ -199,6 +200,11 @@
         }
 
         @Override
+        IPermissionManager getIPermissionManager() {
+            return services.ipermissionManager;
+        }
+
+        @Override
         IBackupManager getIBackupManager() {
             return services.ibackupManager;
         }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 8f0aeea..f6f365e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -52,6 +52,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
+import android.permission.IPermissionManager;
 import android.provider.Settings;
 import android.security.KeyChain;
 import android.telephony.TelephonyManager;
@@ -97,6 +98,7 @@
     public ActivityManagerInternal activityManagerInternal;
     public ActivityTaskManagerInternal activityTaskManagerInternal;
     public final IPackageManager ipackageManager;
+    public final IPermissionManager ipermissionManager;
     public final IBackupManager ibackupManager;
     public final IAudioService iaudioService;
     public final LockPatternUtils lockPatternUtils;
@@ -137,6 +139,7 @@
         activityManagerInternal = mock(ActivityManagerInternal.class);
         activityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
         ipackageManager = mock(IPackageManager.class);
+        ipermissionManager = mock(IPermissionManager.class);
         ibackupManager = mock(IBackupManager.class);
         iaudioService = mock(IAudioService.class);
         lockPatternUtils = mock(LockPatternUtils.class);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java
index 63bf7e7..3e88d93 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProto.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java
@@ -44,7 +44,7 @@
 
         final long token = proto.start(IntervalStatsProto.STRINGPOOL);
         List<String> stringPool;
-        if (proto.isNextField(IntervalStatsProto.StringPool.SIZE)) {
+        if (proto.nextField(IntervalStatsProto.StringPool.SIZE)) {
             stringPool = new ArrayList(proto.readInt(IntervalStatsProto.StringPool.SIZE));
         } else {
             stringPool = new ArrayList();
@@ -66,12 +66,12 @@
 
         final long token = proto.start(fieldId);
         UsageStats stats;
-        if (proto.isNextField(IntervalStatsProto.UsageStats.PACKAGE_INDEX)) {
+        if (proto.nextField(IntervalStatsProto.UsageStats.PACKAGE_INDEX)) {
             // Fast path reading the package name index. Most cases this should work since it is
             // written first
             stats = statsOut.getOrCreateUsageStats(
                     stringPool.get(proto.readInt(IntervalStatsProto.UsageStats.PACKAGE_INDEX) - 1));
-        } else if (proto.isNextField(IntervalStatsProto.UsageStats.PACKAGE)) {
+        } else if (proto.nextField(IntervalStatsProto.UsageStats.PACKAGE)) {
             // No package index, try package name instead
             stats = statsOut.getOrCreateUsageStats(
                     proto.readString(IntervalStatsProto.UsageStats.PACKAGE));
@@ -177,7 +177,7 @@
         }
         String action = null;
         ArrayMap<String, Integer> counts;
-        if (proto.isNextField(IntervalStatsProto.UsageStats.ChooserAction.NAME)) {
+        if (proto.nextField(IntervalStatsProto.UsageStats.ChooserAction.NAME)) {
             // Fast path reading the action name. Most cases this should work since it is written
             // first
             action = proto.readString(IntervalStatsProto.UsageStats.ChooserAction.NAME);
@@ -244,7 +244,7 @@
         boolean configActive = false;
         final Configuration config = new Configuration();
         ConfigurationStats configStats;
-        if (proto.isNextField(IntervalStatsProto.Configuration.CONFIG)) {
+        if (proto.nextField(IntervalStatsProto.Configuration.CONFIG)) {
             // Fast path reading the configuration. Most cases this should work since it is
             // written first
             config.readFromProto(proto, IntervalStatsProto.Configuration.CONFIG);
diff --git a/startop/scripts/app_startup/app_startup_runner.py b/startop/scripts/app_startup/app_startup_runner.py
index 9a608af..7cba780 100755
--- a/startop/scripts/app_startup/app_startup_runner.py
+++ b/startop/scripts/app_startup/app_startup_runner.py
@@ -27,28 +27,40 @@
 #
 
 import argparse
-import asyncio
 import csv
 import itertools
 import os
 import sys
 import tempfile
-import time
-from typing import Any, Callable, Dict, Generic, Iterable, List, NamedTuple, TextIO, Tuple, TypeVar, Optional, Union
+from typing import Any, Callable, Iterable, List, NamedTuple, TextIO, Tuple, \
+    TypeVar, Union
+
+# local import
+DIR = os.path.abspath(os.path.dirname(__file__))
+sys.path.append(os.path.dirname(DIR))
+import app_startup.run_app_with_prefetch as run_app_with_prefetch
+import app_startup.lib.args_utils as args_utils
+from app_startup.lib.data_frame import DataFrame
+import lib.cmd_utils as cmd_utils
+import lib.print_utils as print_utils
 
 # The following command line options participate in the combinatorial generation.
 # All other arguments have a global effect.
-_COMBINATORIAL_OPTIONS=['packages', 'readaheads', 'compiler_filters']
-_TRACING_READAHEADS=['mlock', 'fadvise']
-_FORWARD_OPTIONS={'loop_count': '--count'}
-_RUN_SCRIPT=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'run_app_with_prefetch')
+_COMBINATORIAL_OPTIONS = ['package', 'readahead', 'compiler_filter', 'activity']
+_TRACING_READAHEADS = ['mlock', 'fadvise']
+_FORWARD_OPTIONS = {'loop_count': '--count'}
+_RUN_SCRIPT = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                           'run_app_with_prefetch.py')
 
-RunCommandArgs = NamedTuple('RunCommandArgs', [('package', str), ('readahead', str), ('compiler_filter', Optional[str])])
-CollectorPackageInfo = NamedTuple('CollectorPackageInfo', [('package', str), ('compiler_filter', str)])
-_COLLECTOR_SCRIPT=os.path.join(os.path.dirname(os.path.realpath(__file__)), '../iorap/collector')
-_COLLECTOR_TIMEOUT_MULTIPLIER = 2 # take the regular --timeout and multiply by 2; systrace starts up slowly.
+CollectorPackageInfo = NamedTuple('CollectorPackageInfo',
+                                  [('package', str), ('compiler_filter', str)])
+_COLLECTOR_SCRIPT = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                 '../iorap/collector')
+_COLLECTOR_TIMEOUT_MULTIPLIER = 10  # take the regular --timeout and multiply
+# by 2; systrace starts up slowly.
 
-_UNLOCK_SCREEN_SCRIPT=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'unlock_screen')
+_UNLOCK_SCREEN_SCRIPT = os.path.join(
+    os.path.dirname(os.path.realpath(__file__)), 'unlock_screen')
 
 # This must be the only mutable global variable. All other global variables are constants to avoid magic literals.
 _debug = False  # See -d/--debug flag.
@@ -56,105 +68,70 @@
 
 # Type hinting names.
 T = TypeVar('T')
-NamedTupleMeta = Callable[..., T]  # approximation of a (S : NamedTuple<T> where S() == T) metatype.
+NamedTupleMeta = Callable[
+    ..., T]  # approximation of a (S : NamedTuple<T> where S() == T) metatype.
 
 def parse_options(argv: List[str] = None):
   """Parse command line arguments and return an argparse Namespace object."""
-  parser = argparse.ArgumentParser(description="Run one or more Android applications under various settings in order to measure startup time.")
+  parser = argparse.ArgumentParser(description="Run one or more Android "
+                                               "applications under various "
+                                               "settings in order to measure "
+                                               "startup time.")
   # argparse considers args starting with - and -- optional in --help, even though required=True.
   # by using a named argument group --help will clearly say that it's required instead of optional.
   required_named = parser.add_argument_group('required named arguments')
-  required_named.add_argument('-p', '--package', action='append', dest='packages', help='package of the application', required=True)
-  required_named.add_argument('-r', '--readahead', action='append', dest='readaheads', help='which readahead mode to use', choices=('warm', 'cold', 'mlock', 'fadvise'), required=True)
+  required_named.add_argument('-p', '--package', action='append',
+                              dest='packages',
+                              help='package of the application', required=True)
+  required_named.add_argument('-r', '--readahead', action='append',
+                              dest='readaheads',
+                              help='which readahead mode to use',
+                              choices=('warm', 'cold', 'mlock', 'fadvise'),
+                              required=True)
 
   # optional arguments
   # use a group here to get the required arguments to appear 'above' the optional arguments in help.
   optional_named = parser.add_argument_group('optional named arguments')
-  optional_named.add_argument('-c', '--compiler-filter', action='append', dest='compiler_filters', help='which compiler filter to use. if omitted it does not enforce the app\'s compiler filter', choices=('speed', 'speed-profile', 'quicken'))
-  optional_named.add_argument('-s', '--simulate', dest='simulate', action='store_true', help='Print which commands will run, but don\'t run the apps')
-  optional_named.add_argument('-d', '--debug', dest='debug', action='store_true', help='Add extra debugging output')
-  optional_named.add_argument('-o', '--output', dest='output', action='store', help='Write CSV output to file.')
-  optional_named.add_argument('-t', '--timeout', dest='timeout', action='store', type=int, help='Timeout after this many seconds when executing a single run.')
-  optional_named.add_argument('-lc', '--loop-count', dest='loop_count', default=1, type=int, action='store', help='How many times to loop a single run.')
-  optional_named.add_argument('-in', '--inodes', dest='inodes', type=str, action='store', help='Path to inodes file (system/extras/pagecache/pagecache.py -d inodes)')
+  optional_named.add_argument('-c', '--compiler-filter', action='append',
+                              dest='compiler_filters',
+                              help='which compiler filter to use. if omitted it does not enforce the app\'s compiler filter',
+                              choices=('speed', 'speed-profile', 'quicken'))
+  optional_named.add_argument('-s', '--simulate', dest='simulate',
+                              action='store_true',
+                              help='Print which commands will run, but don\'t run the apps')
+  optional_named.add_argument('-d', '--debug', dest='debug',
+                              action='store_true',
+                              help='Add extra debugging output')
+  optional_named.add_argument('-o', '--output', dest='output', action='store',
+                              help='Write CSV output to file.')
+  optional_named.add_argument('-t', '--timeout', dest='timeout', action='store',
+                              type=int, default=10,
+                              help='Timeout after this many seconds when executing a single run.')
+  optional_named.add_argument('-lc', '--loop-count', dest='loop_count',
+                              default=1, type=int, action='store',
+                              help='How many times to loop a single run.')
+  optional_named.add_argument('-in', '--inodes', dest='inodes', type=str,
+                              action='store',
+                              help='Path to inodes file (system/extras/pagecache/pagecache.py -d inodes)')
 
   return parser.parse_args(argv)
 
-# TODO: refactor this with a common library file with analyze_metrics.py
-def _debug_print(*args, **kwargs):
-  """Print the args to sys.stderr if the --debug/-d flag was passed in."""
+def make_script_command_with_temp_output(script: str,
+                                         args: List[str],
+                                         **kwargs) -> Tuple[str, TextIO]:
+  """
+  Create a command to run a script given the args.
+  Appends --count <loop_count> --output <tmp-file-name>.
+  Returns a tuple (cmd, tmp_file)
+  """
+  tmp_output_file = tempfile.NamedTemporaryFile(mode='r')
+  cmd = [script] + args
+  for key, value in kwargs.items():
+    cmd += ['--%s' % (key), "%s" % (value)]
   if _debug:
-    print(*args, **kwargs, file=sys.stderr)
-
-def _expand_gen_repr(args):
-  """Like repr but any generator-like object has its iterator consumed
-  and then called repr on."""
-  new_args_list = []
-  for i in args:
-    # detect iterable objects that do not have their own override of __str__
-    if hasattr(i, '__iter__'):
-      to_str = getattr(i, '__str__')
-      if to_str.__objclass__ == object:
-        # the repr for a generator is just type+address, expand it out instead.
-        new_args_list.append([_expand_gen_repr([j])[0] for j in i])
-        continue
-    # normal case: uses the built-in to-string
-    new_args_list.append(i)
-  return new_args_list
-
-def _debug_print_gen(*args, **kwargs):
-  """Like _debug_print but will turn any iterable args into a list."""
-  if not _debug:
-    return
-
-  new_args_list = _expand_gen_repr(args)
-  _debug_print(*new_args_list, **kwargs)
-
-def _debug_print_nd(*args, **kwargs):
-  """Like _debug_print but will turn any NamedTuple-type args into a string."""
-  if not _debug:
-    return
-
-  new_args_list = []
-  for i in args:
-    if hasattr(i, '_field_types'):
-      new_args_list.append("%s: %s" %(i.__name__, i._field_types))
-    else:
-      new_args_list.append(i)
-
-  _debug_print(*new_args_list, **kwargs)
-
-def dict_lookup_any_key(dictionary: dict, *keys: List[Any]):
-  for k in keys:
-    if k in dictionary:
-      return dictionary[k]
-  raise KeyError("None of the keys %s were in the dictionary" %(keys))
-
-def generate_run_combinations(named_tuple: NamedTupleMeta[T], opts_dict: Dict[str, List[Optional[str]]])\
-    -> Iterable[T]:
-  """
-  Create all possible combinations given the values in opts_dict[named_tuple._fields].
-
-  :type T: type annotation for the named_tuple type.
-  :param named_tuple: named tuple type, whose fields are used to make combinations for
-  :param opts_dict: dictionary of keys to value list. keys correspond to the named_tuple fields.
-  :return: an iterable over named_tuple instances.
-  """
-  combinations_list = []
-  for k in named_tuple._fields:
-    # the key can be either singular or plural , e.g. 'package' or 'packages'
-    val = dict_lookup_any_key(opts_dict, k, k + "s")
-
-    # treat {'x': None} key value pairs as if it was [None]
-    # otherwise itertools.product throws an exception about not being able to iterate None.
-    combinations_list.append(val or [None])
-
-  _debug_print("opts_dict: ", opts_dict)
-  _debug_print_nd("named_tuple: ", named_tuple)
-  _debug_print("combinations_list: ", combinations_list)
-
-  for combo in itertools.product(*combinations_list):
-    yield named_tuple(*combo)
+    cmd += ['--verbose']
+  cmd = cmd + ["--output", tmp_output_file.name]
+  return cmd, tmp_output_file
 
 def key_to_cmdline_flag(key: str) -> str:
   """Convert key into a command line flag, e.g. 'foo-bars' -> '--foo-bar' """
@@ -176,230 +153,26 @@
     args.append(value)
   return args
 
-def generate_group_run_combinations(run_combinations: Iterable[NamedTuple], dst_nt: NamedTupleMeta[T])\
-    -> Iterable[Tuple[T, Iterable[NamedTuple]]]:
+def run_collector_script(collector_info: CollectorPackageInfo,
+                         inodes_path: str,
+                         timeout: int,
+                         simulate: bool) -> Tuple[bool, TextIO]:
+  """Run collector to collect prefetching trace. """
+  # collector_args = ["--package", package_name]
+  collector_args = as_run_command(collector_info)
+  # TODO: forward --wait_time for how long systrace runs?
+  # TODO: forward --trace_buffer_size for size of systrace buffer size?
+  collector_cmd, collector_tmp_output_file = make_script_command_with_temp_output(
+      _COLLECTOR_SCRIPT, collector_args, inodes=inodes_path)
 
-  def group_by_keys(src_nt):
-    src_d = src_nt._asdict()
-    # now remove the keys that aren't legal in dst.
-    for illegal_key in set(src_d.keys()) - set(dst_nt._fields):
-      if illegal_key in src_d:
-        del src_d[illegal_key]
+  collector_timeout = timeout and _COLLECTOR_TIMEOUT_MULTIPLIER * timeout
+  (collector_passed, collector_script_output) = \
+    cmd_utils.execute_arbitrary_command(collector_cmd,
+                                        collector_timeout,
+                                        shell=False,
+                                        simulate=simulate)
 
-    return dst_nt(**src_d)
-
-  for args_list_it in itertools.groupby(run_combinations, group_by_keys):
-    (group_key_value, args_it) = args_list_it
-    yield (group_key_value, args_it)
-
-class DataFrame:
-  """Table-like class for storing a 2D cells table with named columns."""
-  def __init__(self, data: Dict[str, List[object]] = {}):
-    """
-    Create a new DataFrame from a dictionary (keys = headers,
-    values = columns).
-    """
-    self._headers = [i for i in data.keys()]
-    self._rows = []
-
-    row_num = 0
-
-    def get_data_row(idx):
-      r = {}
-      for header, header_data in data.items():
-
-        if not len(header_data) > idx:
-          continue
-
-        r[header] = header_data[idx]
-
-      return r
-
-    while True:
-      row_dict = get_data_row(row_num)
-      if len(row_dict) == 0:
-        break
-
-      self._append_row(row_dict.keys(), row_dict.values())
-      row_num = row_num + 1
-
-  def concat_rows(self, other: 'DataFrame') -> None:
-    """
-    In-place concatenate rows of other into the rows of the
-    current DataFrame.
-
-    None is added in pre-existing cells if new headers
-    are introduced.
-    """
-    other_datas = other._data_only()
-
-    other_headers = other.headers
-
-    for d in other_datas:
-      self._append_row(other_headers, d)
-
-  def _append_row(self, headers: List[str], data: List[object]):
-    new_row = {k:v for k,v in zip(headers, data)}
-    self._rows.append(new_row)
-
-    for header in headers:
-      if not header in self._headers:
-        self._headers.append(header)
-
-  def __repr__(self):
-#     return repr(self._rows)
-    repr = ""
-
-    header_list = self._headers_only()
-
-    row_format = u""
-    for header in header_list:
-      row_format = row_format + u"{:>%d}" %(len(header) + 1)
-
-    repr = row_format.format(*header_list) + "\n"
-
-    for v in self._data_only():
-      repr = repr + row_format.format(*v) + "\n"
-
-    return repr
-
-  def __eq__(self, other):
-    if isinstance(other, self.__class__):
-      return self.headers == other.headers and self.data_table == other.data_table
-    else:
-      print("wrong instance", other.__class__)
-      return False
-
-  @property
-  def headers(self) -> List[str]:
-    return [i for i in self._headers_only()]
-
-  @property
-  def data_table(self) -> List[List[object]]:
-    return list(self._data_only())
-
-  @property
-  def data_table_transposed(self) -> List[List[object]]:
-    return list(self._transposed_data())
-
-  @property
-  def data_row_len(self) -> int:
-    return len(self._rows)
-
-  def data_row_at(self, idx) -> List[object]:
-    """
-    Return a single data row at the specified index (0th based).
-
-    Accepts negative indices, e.g. -1 is last row.
-    """
-    row_dict = self._rows[idx]
-    l = []
-
-    for h in self._headers_only():
-      l.append(row_dict.get(h)) # Adds None in blank spots.
-
-    return l
-
-  def copy(self) -> 'DataFrame':
-    """
-    Shallow copy of this DataFrame.
-    """
-    return self.repeat(count=0)
-
-  def repeat(self, count: int) -> 'DataFrame':
-    """
-    Returns a new DataFrame where each row of this dataframe is repeated count times.
-    A repeat of a row is adjacent to other repeats of that same row.
-    """
-    df = DataFrame()
-    df._headers = self._headers.copy()
-
-    rows = []
-    for row in self._rows:
-      for i in range(count):
-        rows.append(row.copy())
-
-    df._rows = rows
-
-    return df
-
-  def merge_data_columns(self, other: 'DataFrame'):
-    """
-    Merge self and another DataFrame by adding the data from other column-wise.
-    For any headers that are the same, data from 'other' is preferred.
-    """
-    for h in other._headers:
-      if not h in self._headers:
-        self._headers.append(h)
-
-    append_rows = []
-
-    for self_dict, other_dict in itertools.zip_longest(self._rows, other._rows):
-      if not self_dict:
-        d = {}
-        append_rows.append(d)
-      else:
-        d = self_dict
-
-      d_other = other_dict
-      if d_other:
-        for k,v in d_other.items():
-          d[k] = v
-
-    for r in append_rows:
-      self._rows.append(r)
-
-  def data_row_reduce(self, fnc) -> 'DataFrame':
-    """
-    Reduces the data row-wise by applying the fnc to each row (column-wise).
-    Empty cells are skipped.
-
-    fnc(Iterable[object]) -> object
-    fnc is applied over every non-empty cell in that column (descending row-wise).
-
-    Example:
-      DataFrame({'a':[1,2,3]}).data_row_reduce(sum) == DataFrame({'a':[6]})
-
-    Returns a new single-row DataFrame.
-    """
-    df = DataFrame()
-    df._headers = self._headers.copy()
-
-    def yield_by_column(header_key):
-      for row_dict in self._rows:
-        val = row_dict.get(header_key)
-        if val:
-          yield val
-
-    new_row_dict = {}
-    for h in df._headers:
-      cell_value = fnc(yield_by_column(h))
-      new_row_dict[h] = cell_value
-
-    df._rows = [new_row_dict]
-    return df
-
-  def _headers_only(self):
-    return self._headers
-
-  def _data_only(self):
-    row_len = len(self._rows)
-
-    for i in range(row_len):
-      yield self.data_row_at(i)
-
-  def _transposed_data(self):
-    return zip(*self._data_only())
-
-def parse_run_script_csv_file_flat(csv_file: TextIO) -> List[int]:
-  """Parse a CSV file full of integers into a flat int list."""
-  csv_reader = csv.reader(csv_file)
-  arr = []
-  for row in csv_reader:
-    for i in row:
-      if i:
-        arr.append(int(i))
-  return arr
+  return collector_passed, collector_tmp_output_file
 
 def parse_run_script_csv_file(csv_file: TextIO) -> DataFrame:
   """Parse a CSV file full of integers into a DataFrame."""
@@ -433,160 +206,52 @@
 
   return DataFrame(d)
 
-def make_script_command_with_temp_output(script: str, args: List[str], **kwargs)\
-    -> Tuple[str, TextIO]:
-  """
-  Create a command to run a script given the args.
-  Appends --count <loop_count> --output <tmp-file-name>.
-  Returns a tuple (cmd, tmp_file)
-  """
-  tmp_output_file = tempfile.NamedTemporaryFile(mode='r')
-  cmd = [script] + args
-  for key, value in kwargs.items():
-    cmd += ['--%s' %(key), "%s" %(value)]
-  if _debug:
-    cmd += ['--verbose']
-  cmd = cmd + ["--output", tmp_output_file.name]
-  return cmd, tmp_output_file
-
-async def _run_command(*args : List[str], timeout: Optional[int] = None) -> Tuple[int, bytes]:
-  # start child process
-  # NOTE: universal_newlines parameter is not supported
-  process = await asyncio.create_subprocess_exec(*args,
-      stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT)
-
-  script_output = b""
-
-  _debug_print("[PID]", process.pid)
-
-#hack
-#  stdout, stderr = await process.communicate()
-#  return (process.returncode, stdout)
-
-  timeout_remaining = timeout
-  time_started = time.time()
-
-  # read line (sequence of bytes ending with b'\n') asynchronously
-  while True:
-    try:
-      line = await asyncio.wait_for(process.stdout.readline(), timeout_remaining)
-      _debug_print("[STDOUT]", line)
-      script_output += line
-
-      if timeout_remaining:
-        time_elapsed = time.time() - time_started
-        timeout_remaining = timeout - time_elapsed
-    except asyncio.TimeoutError:
-      _debug_print("[TIMEDOUT] Process ", process.pid)
-
-#      if process.returncode is not None:
-#        #_debug_print("[WTF] can-write-eof?", process.stdout.can_write_eof())
-#
-#        _debug_print("[TIMEDOUT] Process already terminated")
-#        (remaining_stdout, remaining_stderr) = await process.communicate()
-#        script_output += remaining_stdout
-#
-#        code = await process.wait()
-#        return (code, script_output)
-
-      _debug_print("[TIMEDOUT] Sending SIGTERM.")
-      process.terminate()
-
-      # 1 second timeout for process to handle SIGTERM nicely.
-      try:
-       (remaining_stdout, remaining_stderr) = await asyncio.wait_for(process.communicate(), 5)
-       script_output += remaining_stdout
-      except asyncio.TimeoutError:
-        _debug_print("[TIMEDOUT] Sending SIGKILL.")
-        process.kill()
-
-      # 1 second timeout to finish with SIGKILL.
-      try:
-        (remaining_stdout, remaining_stderr) = await asyncio.wait_for(process.communicate(), 5)
-        script_output += remaining_stdout
-      except asyncio.TimeoutError:
-        # give up, this will leave a zombie process.
-        _debug_print("[TIMEDOUT] SIGKILL failed for process ", process.pid)
-        time.sleep(100)
-        #await process.wait()
-
-      return (-1, script_output)
-    else:
-      if not line: # EOF
-        break
-
-      #if process.returncode is not None:
-      #  _debug_print("[WTF] can-write-eof?", process.stdout.can_write_eof())
-      #  process.stdout.write_eof()
-
-      #if process.stdout.at_eof():
-      #  break
-
-  code = await process.wait() # wait for child process to exit
-  return (code, script_output)
-
-def execute_arbitrary_command(cmd: List[str], simulate: bool, timeout: Optional[int]) -> Tuple[bool, str]:
-  if simulate:
-    print(" ".join(cmd))
-    return (True, "")
-  else:
-    _debug_print("[EXECUTE]", cmd)
-
-    # block until either command finishes or the timeout occurs.
-    loop = asyncio.get_event_loop()
-    (return_code, script_output) = loop.run_until_complete(_run_command(*cmd, timeout=timeout))
-
-    script_output = script_output.decode() # convert bytes to str
-
-    passed = (return_code == 0)
-    _debug_print("[$?]", return_code)
-    if not passed:
-      print("[FAILED, code:%s]" %(return_code), script_output, file=sys.stderr)
-
-    return (passed, script_output)
-
-def execute_run_combos(grouped_run_combos: Iterable[Tuple[CollectorPackageInfo, Iterable[RunCommandArgs]]], simulate: bool, inodes_path: str, timeout: int, loop_count: int, need_trace: bool):
+def execute_run_combos(
+    grouped_run_combos: Iterable[Tuple[CollectorPackageInfo, Iterable[
+      run_app_with_prefetch.RunCommandArgs]]],
+    simulate: bool,
+    inodes_path: str,
+    timeout: int):
   # nothing will work if the screen isn't unlocked first.
-  execute_arbitrary_command([_UNLOCK_SCREEN_SCRIPT], simulate, timeout)
+  cmd_utils.execute_arbitrary_command([_UNLOCK_SCREEN_SCRIPT],
+                                      timeout,
+                                      simulate=simulate,
+                                      shell=False)
 
   for collector_info, run_combos in grouped_run_combos:
-    #collector_args = ["--package", package_name]
-    collector_args = as_run_command(collector_info)
-    # TODO: forward --wait_time for how long systrace runs?
-    # TODO: forward --trace_buffer_size for size of systrace buffer size?
-    collector_cmd, collector_tmp_output_file = make_script_command_with_temp_output(_COLLECTOR_SCRIPT, collector_args, inodes=inodes_path)
+    for combos in run_combos:
+      args = as_run_command(combos)
+      if combos.readahead in _TRACING_READAHEADS:
+        passed, collector_tmp_output_file = run_collector_script(collector_info,
+                                                                 inodes_path,
+                                                                 timeout,
+                                                                 simulate)
+        combos = combos._replace(input=collector_tmp_output_file.name)
 
-    with collector_tmp_output_file:
-      collector_passed = True
-      if need_trace:
-        collector_timeout = timeout and _COLLECTOR_TIMEOUT_MULTIPLIER * timeout
-        (collector_passed, collector_script_output) = execute_arbitrary_command(collector_cmd, simulate, collector_timeout)
-        # TODO: consider to print a ; collector wrote file to <...> into the CSV file so we know it was ran.
+      print_utils.debug_print(combos)
+      output = run_app_with_prefetch.run_test(combos)
 
-      for combos in run_combos:
-        args = as_run_command(combos)
+      yield DataFrame(dict((x, [y]) for x, y in output)) if output else None
 
-        cmd, tmp_output_file = make_script_command_with_temp_output(_RUN_SCRIPT, args, count=loop_count, input=collector_tmp_output_file.name)
-        with tmp_output_file:
-          (passed, script_output) = execute_arbitrary_command(cmd, simulate, timeout)
-          parsed_output = simulate and DataFrame({'fake_ms':[1,2,3]}) or parse_run_script_csv_file(tmp_output_file)
-          yield (passed, script_output, parsed_output)
-
-def gather_results(commands: Iterable[Tuple[bool, str, DataFrame]], key_list: List[str], value_list: List[Tuple[str, ...]]):
-  _debug_print("gather_results: key_list = ", key_list)
-#  yield key_list + ["time(ms)"]
-
+def gather_results(commands: Iterable[Tuple[DataFrame]],
+                   key_list: List[str], value_list: List[Tuple[str, ...]]):
+  print_utils.debug_print("gather_results: key_list = ", key_list)
   stringify_none = lambda s: s is None and "<none>" or s
+  #  yield key_list + ["time(ms)"]
+  for (run_result_list, values) in itertools.zip_longest(commands, value_list):
+    print_utils.debug_print("run_result_list = ", run_result_list)
+    print_utils.debug_print("values = ", values)
 
-  for ((passed, script_output, run_result_list), values) in itertools.zip_longest(commands, value_list):
-    _debug_print("run_result_list = ", run_result_list)
-    _debug_print("values = ", values)
-    if not passed:
+    if not run_result_list:
       continue
 
     # RunCommandArgs(package='com.whatever', readahead='warm', compiler_filter=None)
     # -> {'package':['com.whatever'], 'readahead':['warm'], 'compiler_filter':[None]}
-    values_dict = {k:[v] for k,v in values._asdict().items()}
+    values_dict = {}
+    for k, v in values._asdict().items():
+      if not k in key_list:
+        continue
+      values_dict[k] = [stringify_none(v)]
 
     values_df = DataFrame(values_dict)
     # project 'values_df' to be same number of rows as run_result_list.
@@ -598,7 +263,6 @@
     yield values_df
 
 def eval_and_save_to_csv(output, annotated_result_values):
-
   printed_header = False
 
   csv_writer = csv.writer(output)
@@ -610,9 +274,22 @@
       # TODO: what about when headers change?
 
     for data_row in row.data_table:
+      data_row = [d for d in data_row]
       csv_writer.writerow(data_row)
 
-    output.flush() # see the output live.
+    output.flush()  # see the output live.
+
+def coerce_to_list(opts: dict):
+  """Tranform values of the dictionary to list.
+  For example:
+  1 -> [1], None -> [None], [1,2,3] -> [1,2,3]
+  [[1],[2]] -> [[1],[2]], {1:1, 2:2} -> [{1:1, 2:2}]
+  """
+  result = {}
+  for key in opts:
+    val = opts[key]
+    result[key] = val if issubclass(type(val), list) else [val]
+  return result
 
 def main():
   global _debug
@@ -621,26 +298,34 @@
   _debug = opts.debug
   if _DEBUG_FORCE is not None:
     _debug = _DEBUG_FORCE
-  _debug_print("parsed options: ", opts)
-  need_trace = not not set(opts.readaheads).intersection(set(_TRACING_READAHEADS))
-  if need_trace and not opts.inodes:
-    print("Error: Missing -in/--inodes, required when using a readahead of %s" %(_TRACING_READAHEADS), file=sys.stderr)
-    return 1
+
+  print_utils.DEBUG = _debug
+  cmd_utils.SIMULATE = opts.simulate
+
+  print_utils.debug_print("parsed options: ", opts)
 
   output_file = opts.output and open(opts.output, 'w') or sys.stdout
 
-  combos = lambda: generate_run_combinations(RunCommandArgs, vars(opts))
-  _debug_print_gen("run combinations: ", combos())
+  combos = lambda: args_utils.generate_run_combinations(
+      run_app_with_prefetch.RunCommandArgs,
+      coerce_to_list(vars(opts)),
+      opts.loop_count)
+  print_utils.debug_print_gen("run combinations: ", combos())
 
-  grouped_combos = lambda: generate_group_run_combinations(combos(), CollectorPackageInfo)
-  _debug_print_gen("grouped run combinations: ", grouped_combos())
+  grouped_combos = lambda: args_utils.generate_group_run_combinations(combos(),
+                                                                      CollectorPackageInfo)
 
-  exec = execute_run_combos(grouped_combos(), opts.simulate, opts.inodes, opts.timeout, opts.loop_count, need_trace)
+  print_utils.debug_print_gen("grouped run combinations: ", grouped_combos())
+  exec = execute_run_combos(grouped_combos(),
+                            opts.simulate,
+                            opts.inodes,
+                            opts.timeout)
+
   results = gather_results(exec, _COMBINATORIAL_OPTIONS, combos())
+
   eval_and_save_to_csv(output_file, results)
 
-  return 0
-
+  return 1
 
 if __name__ == '__main__':
   sys.exit(main())
diff --git a/startop/scripts/app_startup/app_startup_runner_test.py b/startop/scripts/app_startup/app_startup_runner_test.py
index fd81667..9aa7014 100755
--- a/startop/scripts/app_startup/app_startup_runner_test.py
+++ b/startop/scripts/app_startup/app_startup_runner_test.py
@@ -31,18 +31,17 @@
 See also https://docs.pytest.org/en/latest/usage.html
 """
 
-# global imports
-from contextlib import contextmanager
 import io
 import shlex
 import sys
 import typing
-
-# pip imports
-import pytest
+# global imports
+from contextlib import contextmanager
 
 # local imports
 import app_startup_runner as asr
+# pip imports
+import pytest
 
 #
 # Argument Parsing Helpers
@@ -91,7 +90,8 @@
   """
   # Combine it with all of the "optional" parameters' default values.
   """
-  d = {'compiler_filters': None, 'simulate': False, 'debug': False, 'output': None, 'timeout': None, 'loop_count': 1, 'inodes': None}
+  d = {'compiler_filters': None, 'simulate': False, 'debug': False,
+       'output': None, 'timeout': 10, 'loop_count': 1, 'inodes': None}
   d.update(kwargs)
   return d
 
@@ -111,7 +111,7 @@
   in default_mock_dict_for_parsed_args.
   """
   req = "--package com.fake.package --readahead warm"
-  return parse_args("%s %s" %(req, str))
+  return parse_args("%s %s" % (req, str))
 
 def test_argparse():
   # missing arguments
@@ -124,15 +124,22 @@
   # required arguments are parsed correctly
   ad = default_dict_for_parsed_args  # assert dict
 
-  assert parse_args("--package xyz --readahead warm") == ad(packages=['xyz'], readaheads=['warm'])
-  assert parse_args("-p xyz -r warm") == ad(packages=['xyz'], readaheads=['warm'])
+  assert parse_args("--package xyz --readahead warm") == ad(packages=['xyz'],
+                                                            readaheads=['warm'])
+  assert parse_args("-p xyz -r warm") == ad(packages=['xyz'],
+                                            readaheads=['warm'])
 
-  assert parse_args("-p xyz -r warm -s") == ad(packages=['xyz'], readaheads=['warm'], simulate=True)
-  assert parse_args("-p xyz -r warm --simulate") == ad(packages=['xyz'], readaheads=['warm'], simulate=True)
+  assert parse_args("-p xyz -r warm -s") == ad(packages=['xyz'],
+                                               readaheads=['warm'],
+                                               simulate=True)
+  assert parse_args("-p xyz -r warm --simulate") == ad(packages=['xyz'],
+                                                       readaheads=['warm'],
+                                                       simulate=True)
 
   # optional arguments are parsed correctly.
   mad = default_mock_dict_for_parsed_args  # mock assert dict
-  assert parse_optional_args("--output filename.csv") == mad(output='filename.csv')
+  assert parse_optional_args("--output filename.csv") == mad(
+    output='filename.csv')
   assert parse_optional_args("-o filename.csv") == mad(output='filename.csv')
 
   assert parse_optional_args("--timeout 123") == mad(timeout=123)
@@ -145,36 +152,6 @@
   assert parse_optional_args("-in baz") == mad(inodes="baz")
 
 
-def generate_run_combinations(*args):
-  # expand out the generator values so that assert x == y works properly.
-  return [i for i in asr.generate_run_combinations(*args)]
-
-def test_generate_run_combinations():
-  blank_nd = typing.NamedTuple('Blank')
-  assert generate_run_combinations(blank_nd, {}) == [()], "empty"
-  assert generate_run_combinations(blank_nd, {'a' : ['a1', 'a2']}) == [()], "empty filter"
-  a_nd = typing.NamedTuple('A', [('a', str)])
-  assert generate_run_combinations(a_nd, {'a': None}) == [(None,)], "None"
-  assert generate_run_combinations(a_nd, {'a': ['a1', 'a2']}) == [('a1',), ('a2',)], "one item"
-  assert generate_run_combinations(a_nd,
-                                   {'a' : ['a1', 'a2'], 'b': ['b1', 'b2']}) == [('a1',), ('a2',)],\
-      "one item filter"
-  ab_nd = typing.NamedTuple('AB', [('a', str), ('b', str)])
-  assert generate_run_combinations(ab_nd,
-                                   {'a': ['a1', 'a2'],
-                                    'b': ['b1', 'b2']}) == [ab_nd('a1', 'b1'),
-                                                            ab_nd('a1', 'b2'),
-                                                            ab_nd('a2', 'b1'),
-                                                            ab_nd('a2', 'b2')],\
-      "two items"
-
-  assert generate_run_combinations(ab_nd,
-                                   {'as': ['a1', 'a2'],
-                                    'bs': ['b1', 'b2']}) == [ab_nd('a1', 'b1'),
-                                                             ab_nd('a1', 'b2'),
-                                                             ab_nd('a2', 'b1'),
-                                                             ab_nd('a2', 'b2')],\
-      "two items plural"
 
 def test_key_to_cmdline_flag():
   assert asr.key_to_cmdline_flag("abc") == "--abc"
@@ -182,138 +159,18 @@
   assert asr.key_to_cmdline_flag("ba_r") == "--ba-r"
   assert asr.key_to_cmdline_flag("ba_zs") == "--ba-z"
 
-
 def test_make_script_command_with_temp_output():
-  cmd_str, tmp_file = asr.make_script_command_with_temp_output("fake_script", args=[], count=1)
+  cmd_str, tmp_file = asr.make_script_command_with_temp_output("fake_script",
+                                                               args=[], count=1)
   with tmp_file:
     assert cmd_str == ["fake_script", "--count", "1", "--output", tmp_file.name]
 
-  cmd_str, tmp_file = asr.make_script_command_with_temp_output("fake_script", args=['a', 'b'], count=2)
+  cmd_str, tmp_file = asr.make_script_command_with_temp_output("fake_script",
+                                                               args=['a', 'b'],
+                                                               count=2)
   with tmp_file:
-    assert cmd_str == ["fake_script", "a", "b", "--count", "2", "--output", tmp_file.name]
-
-def test_parse_run_script_csv_file_flat():
-  # empty file -> empty list
-  f = io.StringIO("")
-  assert asr.parse_run_script_csv_file_flat(f) == []
-
-  # common case
-  f = io.StringIO("1,2,3")
-  assert asr.parse_run_script_csv_file_flat(f) == [1,2,3]
-
-  # ignore trailing comma
-  f = io.StringIO("1,2,3,4,5,")
-  assert asr.parse_run_script_csv_file_flat(f) == [1,2,3,4,5]
-
-def test_data_frame():
-  # trivial empty data frame
-  df = asr.DataFrame()
-  assert df.headers == []
-  assert df.data_table == []
-  assert df.data_table_transposed == []
-
-  # common case, same number of values in each place.
-  df = asr.DataFrame({'TotalTime_ms':[1,2,3], 'Displayed_ms':[4,5,6]})
-  assert df.headers == ['TotalTime_ms', 'Displayed_ms']
-  assert df.data_table == [[1, 4], [2, 5], [3, 6]]
-  assert df.data_table_transposed == [(1, 2, 3), (4, 5, 6)]
-
-  # varying num values.
-  df = asr.DataFrame({'many':[1,2], 'none': []})
-  assert df.headers == ['many', 'none']
-  assert df.data_table == [[1, None], [2, None]]
-  assert df.data_table_transposed == [(1, 2), (None, None)]
-
-  df = asr.DataFrame({'many':[], 'none': [1,2]})
-  assert df.headers == ['many', 'none']
-  assert df.data_table == [[None, 1], [None, 2]]
-  assert df.data_table_transposed == [(None, None), (1, 2)]
-
-  # merge multiple data frames
-  df = asr.DataFrame()
-  df.concat_rows(asr.DataFrame())
-  assert df.headers == []
-  assert df.data_table == []
-  assert df.data_table_transposed == []
-
-  df = asr.DataFrame()
-  df2 = asr.DataFrame({'TotalTime_ms':[1,2,3], 'Displayed_ms':[4,5,6]})
-
-  df.concat_rows(df2)
-  assert df.headers == ['TotalTime_ms', 'Displayed_ms']
-  assert df.data_table == [[1, 4], [2, 5], [3, 6]]
-  assert df.data_table_transposed == [(1, 2, 3), (4, 5, 6)]
-
-  df = asr.DataFrame({'TotalTime_ms':[1,2]})
-  df2 = asr.DataFrame({'Displayed_ms':[4,5]})
-
-  df.concat_rows(df2)
-  assert df.headers == ['TotalTime_ms', 'Displayed_ms']
-  assert df.data_table == [[1, None], [2, None], [None, 4], [None, 5]]
-
-  df = asr.DataFrame({'TotalTime_ms':[1,2]})
-  df2 = asr.DataFrame({'TotalTime_ms': [3, 4], 'Displayed_ms':[5, 6]})
-
-  df.concat_rows(df2)
-  assert df.headers == ['TotalTime_ms', 'Displayed_ms']
-  assert df.data_table == [[1, None], [2, None], [3, 5], [4, 6]]
-
-  # data_row_at
-  df = asr.DataFrame({'TotalTime_ms':[1,2,3], 'Displayed_ms':[4,5,6]})
-  assert df.data_row_at(-1) == [3,6]
-  assert df.data_row_at(2) == [3,6]
-  assert df.data_row_at(1) == [2,5]
-
-  # repeat
-  df = asr.DataFrame({'TotalTime_ms':[1], 'Displayed_ms':[4]})
-  df2 = asr.DataFrame({'TotalTime_ms':[1,1,1], 'Displayed_ms':[4,4,4]})
-  assert df.repeat(3) == df2
-
-  # repeat
-  df = asr.DataFrame({'TotalTime_ms':[1,1,1], 'Displayed_ms':[4,4,4]})
-  assert df.data_row_len == 3
-  df = asr.DataFrame({'TotalTime_ms':[1,1]})
-  assert df.data_row_len == 2
-
-  # repeat
-  df = asr.DataFrame({'TotalTime_ms':[1,1,1], 'Displayed_ms':[4,4,4]})
-  assert df.data_row_len == 3
-  df = asr.DataFrame({'TotalTime_ms':[1,1]})
-  assert df.data_row_len == 2
-
-  # data_row_reduce
-  df = asr.DataFrame({'TotalTime_ms':[1,1,1], 'Displayed_ms':[4,4,4]})
-  df_sum = asr.DataFrame({'TotalTime_ms':[3], 'Displayed_ms':[12]})
-  assert df.data_row_reduce(sum) == df_sum
-
-  # merge_data_columns
-  df = asr.DataFrame({'TotalTime_ms':[1,2,3]})
-  df2 = asr.DataFrame({'Displayed_ms':[3,4,5,6]})
-
-  df.merge_data_columns(df2)
-  assert df == asr.DataFrame({'TotalTime_ms':[1,2,3], 'Displayed_ms':[3,4,5,6]})
-
-  df = asr.DataFrame({'TotalTime_ms':[1,2,3]})
-  df2 = asr.DataFrame({'Displayed_ms':[3,4]})
-
-  df.merge_data_columns(df2)
-  assert df == asr.DataFrame({'TotalTime_ms':[1,2,3], 'Displayed_ms':[3,4]})
-
-  df = asr.DataFrame({'TotalTime_ms':[1,2,3]})
-  df2 = asr.DataFrame({'TotalTime_ms':[10,11]})
-
-  df.merge_data_columns(df2)
-  assert df == asr.DataFrame({'TotalTime_ms':[10,11,3]})
-
-  df = asr.DataFrame({'TotalTime_ms':[]})
-  df2 = asr.DataFrame({'TotalTime_ms':[10,11]})
-
-  df.merge_data_columns(df2)
-  assert df == asr.DataFrame({'TotalTime_ms':[10,11]})
-
-
-
-
+    assert cmd_str == ["fake_script", "a", "b", "--count", "2", "--output",
+                       tmp_file.name]
 
 def test_parse_run_script_csv_file():
   # empty file -> empty list
diff --git a/startop/scripts/app_startup/lib/args_utils.py b/startop/scripts/app_startup/lib/args_utils.py
new file mode 100644
index 0000000..080f3b5
--- /dev/null
+++ b/startop/scripts/app_startup/lib/args_utils.py
@@ -0,0 +1,77 @@
+import itertools
+import os
+import sys
+from typing import Any, Callable, Dict, Iterable, List, NamedTuple, Tuple, \
+    TypeVar, Optional
+
+# local import
+sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(
+    os.path.abspath(__file__)))))
+import lib.print_utils as print_utils
+
+T = TypeVar('T')
+NamedTupleMeta = Callable[
+    ..., T]  # approximation of a (S : NamedTuple<T> where S() == T) metatype.
+FilterFuncType = Callable[[NamedTuple], bool]
+
+def dict_lookup_any_key(dictionary: dict, *keys: List[Any]):
+  for k in keys:
+    if k in dictionary:
+      return dictionary[k]
+
+
+  print_utils.debug_print("None of the keys {} were in the dictionary".format(
+      keys))
+  return [None]
+
+def generate_run_combinations(named_tuple: NamedTupleMeta[T],
+                              opts_dict: Dict[str, List[Optional[object]]],
+                              loop_count: int = 1) -> Iterable[T]:
+  """
+  Create all possible combinations given the values in opts_dict[named_tuple._fields].
+
+  :type T: type annotation for the named_tuple type.
+  :param named_tuple: named tuple type, whose fields are used to make combinations for
+  :param opts_dict: dictionary of keys to value list. keys correspond to the named_tuple fields.
+  :param loop_count: number of repetitions.
+  :return: an iterable over named_tuple instances.
+  """
+  combinations_list = []
+  for k in named_tuple._fields:
+    # the key can be either singular or plural , e.g. 'package' or 'packages'
+    val = dict_lookup_any_key(opts_dict, k, k + "s")
+
+    # treat {'x': None} key value pairs as if it was [None]
+    # otherwise itertools.product throws an exception about not being able to iterate None.
+    combinations_list.append(val or [None])
+
+  print_utils.debug_print("opts_dict: ", opts_dict)
+  print_utils.debug_print_nd("named_tuple: ", named_tuple)
+  print_utils.debug_print("combinations_list: ", combinations_list)
+
+  for i in range(loop_count):
+    for combo in itertools.product(*combinations_list):
+      yield named_tuple(*combo)
+
+def filter_run_combinations(named_tuple: NamedTuple,
+                            filters: List[FilterFuncType]) -> bool:
+  for filter in filters:
+    if filter(named_tuple):
+      return False
+  return True
+
+def generate_group_run_combinations(run_combinations: Iterable[NamedTuple],
+                                    dst_nt: NamedTupleMeta[T]) \
+    -> Iterable[Tuple[T, Iterable[NamedTuple]]]:
+  def group_by_keys(src_nt):
+    src_d = src_nt._asdict()
+    # now remove the keys that aren't legal in dst.
+    for illegal_key in set(src_d.keys()) - set(dst_nt._fields):
+      if illegal_key in src_d:
+        del src_d[illegal_key]
+
+    return dst_nt(**src_d)
+
+  for args_list_it in itertools.groupby(run_combinations, group_by_keys):
+    (group_key_value, args_it) = args_list_it
+    yield (group_key_value, args_it)
diff --git a/startop/scripts/app_startup/lib/args_utils_test.py b/startop/scripts/app_startup/lib/args_utils_test.py
new file mode 100644
index 0000000..4b7e0fa
--- /dev/null
+++ b/startop/scripts/app_startup/lib/args_utils_test.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+#
+# Copyright 2018, 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.
+#
+
+"""Unit tests for the args_utils.py script."""
+
+import typing
+
+import args_utils
+
+def generate_run_combinations(*args):
+  # expand out the generator values so that assert x == y works properly.
+  return [i for i in args_utils.generate_run_combinations(*args)]
+
+def test_generate_run_combinations():
+  blank_nd = typing.NamedTuple('Blank')
+  assert generate_run_combinations(blank_nd, {}, 1) == [()], "empty"
+  assert generate_run_combinations(blank_nd, {'a': ['a1', 'a2']}) == [
+    ()], "empty filter"
+  a_nd = typing.NamedTuple('A', [('a', str)])
+  assert generate_run_combinations(a_nd, {'a': None}) == [(None,)], "None"
+  assert generate_run_combinations(a_nd, {'a': ['a1', 'a2']}) == [('a1',), (
+    'a2',)], "one item"
+  assert generate_run_combinations(a_nd,
+                                   {'a': ['a1', 'a2'], 'b': ['b1', 'b2']}) == [
+           ('a1',), ('a2',)], \
+    "one item filter"
+  assert generate_run_combinations(a_nd, {'a': ['a1', 'a2']}, 2) == [('a1',), (
+    'a2',), ('a1',), ('a2',)], "one item"
+  ab_nd = typing.NamedTuple('AB', [('a', str), ('b', str)])
+  assert generate_run_combinations(ab_nd,
+                                   {'a': ['a1', 'a2'],
+                                    'b': ['b1', 'b2']}) == [ab_nd('a1', 'b1'),
+                                                            ab_nd('a1', 'b2'),
+                                                            ab_nd('a2', 'b1'),
+                                                            ab_nd('a2', 'b2')], \
+    "two items"
+
+  assert generate_run_combinations(ab_nd,
+                                   {'as': ['a1', 'a2'],
+                                    'bs': ['b1', 'b2']}) == [ab_nd('a1', 'b1'),
+                                                             ab_nd('a1', 'b2'),
+                                                             ab_nd('a2', 'b1'),
+                                                             ab_nd('a2', 'b2')], \
+    "two items plural"
diff --git a/startop/scripts/app_startup/lib/data_frame.py b/startop/scripts/app_startup/lib/data_frame.py
new file mode 100644
index 0000000..20a2308
--- /dev/null
+++ b/startop/scripts/app_startup/lib/data_frame.py
@@ -0,0 +1,201 @@
+import itertools
+from typing import Dict, List
+
+class DataFrame:
+  """Table-like class for storing a 2D cells table with named columns."""
+  def __init__(self, data: Dict[str, List[object]] = {}):
+    """
+    Create a new DataFrame from a dictionary (keys = headers,
+    values = columns).
+    """
+    self._headers = [i for i in data.keys()]
+    self._rows = []
+
+    row_num = 0
+
+    def get_data_row(idx):
+      r = {}
+      for header, header_data in data.items():
+
+        if not len(header_data) > idx:
+          continue
+
+        r[header] = header_data[idx]
+
+      return r
+
+    while True:
+      row_dict = get_data_row(row_num)
+      if len(row_dict) == 0:
+        break
+
+      self._append_row(row_dict.keys(), row_dict.values())
+      row_num = row_num + 1
+
+  def concat_rows(self, other: 'DataFrame') -> None:
+    """
+    In-place concatenate rows of other into the rows of the
+    current DataFrame.
+
+    None is added in pre-existing cells if new headers
+    are introduced.
+    """
+    other_datas = other._data_only()
+
+    other_headers = other.headers
+
+    for d in other_datas:
+      self._append_row(other_headers, d)
+
+  def _append_row(self, headers: List[str], data: List[object]):
+    new_row = {k:v for k,v in zip(headers, data)}
+    self._rows.append(new_row)
+
+    for header in headers:
+      if not header in self._headers:
+        self._headers.append(header)
+
+  def __repr__(self):
+#     return repr(self._rows)
+    repr = ""
+
+    header_list = self._headers_only()
+
+    row_format = u""
+    for header in header_list:
+      row_format = row_format + u"{:>%d}" %(len(header) + 1)
+
+    repr = row_format.format(*header_list) + "\n"
+
+    for v in self._data_only():
+      repr = repr + row_format.format(*v) + "\n"
+
+    return repr
+
+  def __eq__(self, other):
+    if isinstance(other, self.__class__):
+      return self.headers == other.headers and self.data_table == other.data_table
+    else:
+      print("wrong instance", other.__class__)
+      return False
+
+  @property
+  def headers(self) -> List[str]:
+    return [i for i in self._headers_only()]
+
+  @property
+  def data_table(self) -> List[List[object]]:
+    return list(self._data_only())
+
+  @property
+  def data_table_transposed(self) -> List[List[object]]:
+    return list(self._transposed_data())
+
+  @property
+  def data_row_len(self) -> int:
+    return len(self._rows)
+
+  def data_row_at(self, idx) -> List[object]:
+    """
+    Return a single data row at the specified index (0th based).
+
+    Accepts negative indices, e.g. -1 is last row.
+    """
+    row_dict = self._rows[idx]
+    l = []
+
+    for h in self._headers_only():
+      l.append(row_dict.get(h)) # Adds None in blank spots.
+
+    return l
+
+  def copy(self) -> 'DataFrame':
+    """
+    Shallow copy of this DataFrame.
+    """
+    return self.repeat(count=0)
+
+  def repeat(self, count: int) -> 'DataFrame':
+    """
+    Returns a new DataFrame where each row of this dataframe is repeated count times.
+    A repeat of a row is adjacent to other repeats of that same row.
+    """
+    df = DataFrame()
+    df._headers = self._headers.copy()
+
+    rows = []
+    for row in self._rows:
+      for i in range(count):
+        rows.append(row.copy())
+
+    df._rows = rows
+
+    return df
+
+  def merge_data_columns(self, other: 'DataFrame'):
+    """
+    Merge self and another DataFrame by adding the data from other column-wise.
+    For any headers that are the same, data from 'other' is preferred.
+    """
+    for h in other._headers:
+      if not h in self._headers:
+        self._headers.append(h)
+
+    append_rows = []
+
+    for self_dict, other_dict in itertools.zip_longest(self._rows, other._rows):
+      if not self_dict:
+        d = {}
+        append_rows.append(d)
+      else:
+        d = self_dict
+
+      d_other = other_dict
+      if d_other:
+        for k,v in d_other.items():
+          d[k] = v
+
+    for r in append_rows:
+      self._rows.append(r)
+
+  def data_row_reduce(self, fnc) -> 'DataFrame':
+    """
+    Reduces the data row-wise by applying the fnc to each row (column-wise).
+    Empty cells are skipped.
+
+    fnc(Iterable[object]) -> object
+    fnc is applied over every non-empty cell in that column (descending row-wise).
+
+    Example:
+      DataFrame({'a':[1,2,3]}).data_row_reduce(sum) == DataFrame({'a':[6]})
+
+    Returns a new single-row DataFrame.
+    """
+    df = DataFrame()
+    df._headers = self._headers.copy()
+
+    def yield_by_column(header_key):
+      for row_dict in self._rows:
+        val = row_dict.get(header_key)
+        if val:
+          yield val
+
+    new_row_dict = {}
+    for h in df._headers:
+      cell_value = fnc(yield_by_column(h))
+      new_row_dict[h] = cell_value
+
+    df._rows = [new_row_dict]
+    return df
+
+  def _headers_only(self):
+    return self._headers
+
+  def _data_only(self):
+    row_len = len(self._rows)
+
+    for i in range(row_len):
+      yield self.data_row_at(i)
+
+  def _transposed_data(self):
+    return zip(*self._data_only())
\ No newline at end of file
diff --git a/startop/scripts/app_startup/lib/data_frame_test.py b/startop/scripts/app_startup/lib/data_frame_test.py
new file mode 100644
index 0000000..1cbc1cb
--- /dev/null
+++ b/startop/scripts/app_startup/lib/data_frame_test.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+#
+# Copyright 2018, 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.
+#
+
+"""Unit tests for the data_frame.py script."""
+
+from data_frame import DataFrame
+
+def test_data_frame():
+  # trivial empty data frame
+  df = DataFrame()
+  assert df.headers == []
+  assert df.data_table == []
+  assert df.data_table_transposed == []
+
+  # common case, same number of values in each place.
+  df = DataFrame({'TotalTime_ms': [1, 2, 3], 'Displayed_ms': [4, 5, 6]})
+  assert df.headers == ['TotalTime_ms', 'Displayed_ms']
+  assert df.data_table == [[1, 4], [2, 5], [3, 6]]
+  assert df.data_table_transposed == [(1, 2, 3), (4, 5, 6)]
+
+  # varying num values.
+  df = DataFrame({'many': [1, 2], 'none': []})
+  assert df.headers == ['many', 'none']
+  assert df.data_table == [[1, None], [2, None]]
+  assert df.data_table_transposed == [(1, 2), (None, None)]
+
+  df = DataFrame({'many': [], 'none': [1, 2]})
+  assert df.headers == ['many', 'none']
+  assert df.data_table == [[None, 1], [None, 2]]
+  assert df.data_table_transposed == [(None, None), (1, 2)]
+
+  # merge multiple data frames
+  df = DataFrame()
+  df.concat_rows(DataFrame())
+  assert df.headers == []
+  assert df.data_table == []
+  assert df.data_table_transposed == []
+
+  df = DataFrame()
+  df2 = DataFrame({'TotalTime_ms': [1, 2, 3], 'Displayed_ms': [4, 5, 6]})
+
+  df.concat_rows(df2)
+  assert df.headers == ['TotalTime_ms', 'Displayed_ms']
+  assert df.data_table == [[1, 4], [2, 5], [3, 6]]
+  assert df.data_table_transposed == [(1, 2, 3), (4, 5, 6)]
+
+  df = DataFrame({'TotalTime_ms': [1, 2]})
+  df2 = DataFrame({'Displayed_ms': [4, 5]})
+
+  df.concat_rows(df2)
+  assert df.headers == ['TotalTime_ms', 'Displayed_ms']
+  assert df.data_table == [[1, None], [2, None], [None, 4], [None, 5]]
+
+  df = DataFrame({'TotalTime_ms': [1, 2]})
+  df2 = DataFrame({'TotalTime_ms': [3, 4], 'Displayed_ms': [5, 6]})
+
+  df.concat_rows(df2)
+  assert df.headers == ['TotalTime_ms', 'Displayed_ms']
+  assert df.data_table == [[1, None], [2, None], [3, 5], [4, 6]]
+
+  # data_row_at
+  df = DataFrame({'TotalTime_ms': [1, 2, 3], 'Displayed_ms': [4, 5, 6]})
+  assert df.data_row_at(-1) == [3, 6]
+  assert df.data_row_at(2) == [3, 6]
+  assert df.data_row_at(1) == [2, 5]
+
+  # repeat
+  df = DataFrame({'TotalTime_ms': [1], 'Displayed_ms': [4]})
+  df2 = DataFrame({'TotalTime_ms': [1, 1, 1], 'Displayed_ms': [4, 4, 4]})
+  assert df.repeat(3) == df2
+
+  # repeat
+  df = DataFrame({'TotalTime_ms': [1, 1, 1], 'Displayed_ms': [4, 4, 4]})
+  assert df.data_row_len == 3
+  df = DataFrame({'TotalTime_ms': [1, 1]})
+  assert df.data_row_len == 2
+
+  # repeat
+  df = DataFrame({'TotalTime_ms': [1, 1, 1], 'Displayed_ms': [4, 4, 4]})
+  assert df.data_row_len == 3
+  df = DataFrame({'TotalTime_ms': [1, 1]})
+  assert df.data_row_len == 2
+
+  # data_row_reduce
+  df = DataFrame({'TotalTime_ms': [1, 1, 1], 'Displayed_ms': [4, 4, 4]})
+  df_sum = DataFrame({'TotalTime_ms': [3], 'Displayed_ms': [12]})
+  assert df.data_row_reduce(sum) == df_sum
+
+  # merge_data_columns
+  df = DataFrame({'TotalTime_ms': [1, 2, 3]})
+  df2 = DataFrame({'Displayed_ms': [3, 4, 5, 6]})
+
+  df.merge_data_columns(df2)
+  assert df == DataFrame(
+    {'TotalTime_ms': [1, 2, 3], 'Displayed_ms': [3, 4, 5, 6]})
+
+  df = DataFrame({'TotalTime_ms': [1, 2, 3]})
+  df2 = DataFrame({'Displayed_ms': [3, 4]})
+
+  df.merge_data_columns(df2)
+  assert df == DataFrame(
+    {'TotalTime_ms': [1, 2, 3], 'Displayed_ms': [3, 4]})
+
+  df = DataFrame({'TotalTime_ms': [1, 2, 3]})
+  df2 = DataFrame({'TotalTime_ms': [10, 11]})
+
+  df.merge_data_columns(df2)
+  assert df == DataFrame({'TotalTime_ms': [10, 11, 3]})
+
+  df = DataFrame({'TotalTime_ms': []})
+  df2 = DataFrame({'TotalTime_ms': [10, 11]})
+
+  df.merge_data_columns(df2)
+  assert df == DataFrame({'TotalTime_ms': [10, 11]})
diff --git a/startop/scripts/app_startup/query_compiler_filter.py b/startop/scripts/app_startup/query_compiler_filter.py
index dc97c66..ea14264 100755
--- a/startop/scripts/app_startup/query_compiler_filter.py
+++ b/startop/scripts/app_startup/query_compiler_filter.py
@@ -31,13 +31,15 @@
 #
 
 import argparse
-import sys
+import os
 import re
+import sys
 
 # TODO: refactor this with a common library file with analyze_metrics.py
-import app_startup_runner
-from app_startup_runner import _debug_print
-from app_startup_runner import execute_arbitrary_command
+DIR = os.path.abspath(os.path.dirname(__file__))
+sys.path.append(os.path.dirname(DIR))
+import lib.cmd_utils as cmd_utils
+import lib.print_utils as print_utils
 
 from typing import List, NamedTuple, Iterable
 
@@ -74,7 +76,11 @@
       x86: [status=quicken] [reason=install]
 """ %(package, package, package, package)
 
-  code, res = execute_arbitrary_command(['adb', 'shell', 'dumpsys', 'package', package], simulate=False, timeout=5)
+  code, res = cmd_utils.execute_arbitrary_command(['adb', 'shell', 'dumpsys',
+                                                   'package', package],
+                                                  simulate=False,
+                                                  timeout=5,
+                                                  shell=False)
   if code:
     return res
   else:
@@ -115,7 +121,7 @@
       line = str_lines[line_num]
       current_indent = get_indent_level(line)
 
-      _debug_print("INDENT=%d, LINE=%s" %(current_indent, line))
+      print_utils.debug_print("INDENT=%d, LINE=%s" %(current_indent, line))
 
       current_label = line.lstrip()
 
@@ -135,7 +141,7 @@
       break
 
   new_remainder = str_lines[line_num::]
-  _debug_print("NEW REMAINDER: ", new_remainder)
+  print_utils.debug_print("NEW REMAINDER: ", new_remainder)
 
   parse_tree = ParseTree(label, children)
   return ParseResult(new_remainder, parse_tree)
@@ -159,7 +165,7 @@
 def find_first_compiler_filter(dexopt_state: DexoptState, package: str, instruction_set: str) -> str:
   lst = find_all_compiler_filters(dexopt_state, package)
 
-  _debug_print("all compiler filters: ", lst)
+  print_utils.debug_print("all compiler filters: ", lst)
 
   for compiler_filter_info in lst:
     if not instruction_set:
@@ -180,10 +186,10 @@
   if not package_tree:
     raise AssertionError("Could not find any package subtree for package %s" %(package))
 
-  _debug_print("package tree: ", package_tree)
+  print_utils.debug_print("package tree: ", package_tree)
 
   for path_tree in find_parse_children(package_tree, "path: "):
-    _debug_print("path tree: ", path_tree)
+    print_utils.debug_print("path tree: ", path_tree)
 
     matchre = re.compile("([^:]+):\s+\[status=([^\]]+)\]\s+\[reason=([^\]]+)\]")
 
@@ -198,16 +204,16 @@
 
 def main() -> int:
   opts = parse_options()
-  app_startup_runner._debug = opts.debug
+  cmd_utils._debug = opts.debug
   if _DEBUG_FORCE is not None:
-    app_startup_runner._debug = _DEBUG_FORCE
-  _debug_print("parsed options: ", opts)
+    cmd_utils._debug = _DEBUG_FORCE
+  print_utils.debug_print("parsed options: ", opts)
 
   # Note: This can often 'fail' if the package isn't actually installed.
   package_dumpsys = remote_dumpsys_package(opts.package, opts.simulate)
-  _debug_print("package dumpsys: ", package_dumpsys)
+  print_utils.debug_print("package dumpsys: ", package_dumpsys)
   dumpsys_parse_tree = parse_tab_tree(package_dumpsys, package_dumpsys)
-  _debug_print("parse tree: ", dumpsys_parse_tree)
+  print_utils.debug_print("parse tree: ", dumpsys_parse_tree)
   dexopt_state = parse_dexopt_state(dumpsys_parse_tree)
 
   filter = find_first_compiler_filter(dexopt_state, opts.package, opts.instruction_set)
diff --git a/startop/scripts/app_startup/run_app_with_prefetch.py b/startop/scripts/app_startup/run_app_with_prefetch.py
index 052db9d..8a9135b 100644
--- a/startop/scripts/app_startup/run_app_with_prefetch.py
+++ b/startop/scripts/app_startup/run_app_with_prefetch.py
@@ -30,7 +30,7 @@
 import os
 import sys
 import time
-from typing import List, Tuple, Optional
+from typing import List, Tuple, Optional, NamedTuple
 
 # local imports
 import lib.adb_utils as adb_utils
@@ -47,10 +47,20 @@
 import lib.cmd_utils as cmd_utils
 import iorap.lib.iorapd_utils as iorapd_utils
 
+RunCommandArgs = NamedTuple('RunCommandArgs',
+                            [('package', str),
+                             ('readahead', str),
+                             ('activity', Optional[str]),
+                             ('compiler_filter', Optional[str]),
+                             ('timeout', Optional[int]),
+                             ('debug', bool),
+                             ('simulate', bool),
+                             ('input', Optional[str])])
+
 def parse_options(argv: List[str] = None):
   """Parses command line arguments and return an argparse Namespace object."""
   parser = argparse.ArgumentParser(
-    description='Run an Android application once and measure startup time.'
+      description='Run an Android application once and measure startup time.'
   )
 
   required_named = parser.add_argument_group('required named arguments')
@@ -91,43 +101,44 @@
 
   return parser.parse_args(argv)
 
-def validate_options(opts: argparse.Namespace) -> bool:
+def validate_options(args: argparse.Namespace) -> Tuple[bool, RunCommandArgs]:
   """Validates the activity and trace file if needed.
 
   Returns:
     A bool indicates whether the activity is valid and trace file exists if
     necessary.
   """
-  needs_trace_file = (opts.readahead != 'cold' and opts.readahead != 'warm')
-  if needs_trace_file and (opts.input is None or
-                           not os.path.exists(opts.input)):
+  needs_trace_file = (args.readahead != 'cold' and args.readahead != 'warm')
+  if needs_trace_file and (args.input is None or
+                           not os.path.exists(args.input)):
     print_utils.error_print('--input not specified!')
-    return False
+    return False, args
 
-  if opts.simulate:
-    opts.activity = 'act'
+  if args.simulate:
+    args = args._replace(activity='act')
 
-  if not opts.activity:
-    _, opts.activity = cmd_utils.run_shell_func(IORAP_COMMON_BASH_SCRIPT,
-                                                'get_activity_name',
-                                                [opts.package])
+  if not args.activity:
+    _, activity = cmd_utils.run_shell_func(IORAP_COMMON_BASH_SCRIPT,
+                                           'get_activity_name',
+                                           [args.package])
+    args = args._replace(activity=activity)
 
-  if not opts.activity:
+  if not args.activity:
     print_utils.error_print('Activity name could not be found, '
-                              'invalid package name?!')
-    return False
+                            'invalid package name?!')
+    return False, args
 
   # Install necessary trace file. This must be after the activity checking.
   if needs_trace_file:
     passed = iorapd_utils.iorapd_compiler_install_trace_file(
-      opts.package, opts.activity, opts.input)
+        args.package, args.activity, args.input)
     if not cmd_utils.SIMULATE and not passed:
       print_utils.error_print('Failed to install compiled TraceFile.pb for '
                               '"{}/{}"'.
-                                format(opts.package, opts.activity))
-      return False
+                                format(args.package, args.activity))
+      return False, args
 
-  return True
+  return True, args
 
 def set_up_adb_env():
   """Sets up adb environment."""
@@ -149,17 +160,18 @@
 
   passed, current_compiler_filter_info = \
     cmd_utils.run_shell_command(
-      '{} --package {}'.format(os.path.join(DIR, 'query_compiler_filter.py'),
-                               package))
+        '{} --package {}'.format(os.path.join(DIR, 'query_compiler_filter.py'),
+                                 package))
 
   if passed != 0:
     return passed
 
   # TODO: call query_compiler_filter directly as a python function instead of
   #  these shell calls.
-  current_compiler_filter, current_reason, current_isa = current_compiler_filter_info.split(' ')
+  current_compiler_filter, current_reason, current_isa = \
+    current_compiler_filter_info.split(' ')
   print_utils.debug_print('Compiler Filter={} Reason={} Isa={}'.format(
-    current_compiler_filter, current_reason, current_isa))
+      current_compiler_filter, current_reason, current_isa))
 
   # Don't trust reasons that aren't 'unknown' because that means
   #  we didn't manually force the compilation filter.
@@ -202,7 +214,7 @@
     print_utils.debug_print('metric: "{metric_name}", '
                             'value: "{metric_value}" '.
                               format(metric_name=metric_name,
-                                     metric_value=metric_value))
+                                   metric_value=metric_value))
 
     all_metrics.append((metric_name, metric_value))
   return all_metrics
@@ -230,7 +242,7 @@
   """
   total_time = _parse_total_time(am_start_output)
   displayed_time = adb_utils.blocking_wait_for_logcat_displayed_time(
-    pre_launch_timestamp, package, timeout)
+      pre_launch_timestamp, package, timeout)
 
   return 'TotalTime={}\nDisplayedTime={}'.format(total_time, displayed_time)
 
@@ -268,10 +280,10 @@
                                                '"{DIR}/launch_application" '
                                                '"{package}" '
                                                '"{activity}"'
-                                                .format(timeout=timeout,
-                                                        DIR=DIR,
-                                                        package=package,
-                                                        activity=activity))
+                                                 .format(timeout=timeout,
+                                                       DIR=DIR,
+                                                       package=package,
+                                                       activity=activity))
   if not passed and not simulate:
     return None
 
@@ -285,7 +297,7 @@
     results = parse_metrics_output(output, simulate)
 
   passed = perform_post_launch_cleanup(
-    readahead, package, activity, timeout, debug, pre_launch_timestamp)
+      readahead, package, activity, timeout, debug, pre_launch_timestamp)
   if not passed and not simulate:
     print_utils.error_print('Cannot perform post launch cleanup!')
     return None
@@ -306,10 +318,10 @@
   """
   if readahead != 'warm' and readahead != 'cold':
     passed = iorapd_utils.wait_for_iorapd_finish(package,
-                                               activity,
-                                               timeout,
-                                               debug,
-                                               logcat_timestamp)
+                                                 activity,
+                                                 timeout,
+                                                 debug,
+                                                 logcat_timestamp)
 
     if not passed:
       return passed
@@ -319,16 +331,16 @@
   # Don't need to do anything for warm or cold.
   return True
 
-def run_test(opts: argparse.Namespace) -> List[Tuple[str, str]]:
+def run_test(args: RunCommandArgs) -> List[Tuple[str, str]]:
   """Runs one test using given options.
 
   Returns:
-    A list of tuples that including metric name, metric value and anything left.
+    A list of tuples that including metric name, metric value.
   """
-  print_utils.DEBUG = opts.debug
-  cmd_utils.SIMULATE = opts.simulate
+  print_utils.DEBUG = args.debug
+  cmd_utils.SIMULATE = args.simulate
 
-  passed = validate_options(opts)
+  passed, args = validate_options(args)
   if not passed:
     return None
 
@@ -337,15 +349,22 @@
   # Ensure the APK is currently compiled with whatever we passed in
   # via --compiler-filter.
   # No-op if this option was not passed in.
-  if not configure_compiler_filter(opts.compiler_filter, opts.package,
-                                   opts.activity):
+  if not configure_compiler_filter(args.compiler_filter, args.package,
+                                   args.activity):
     return None
 
-  return run(opts.readahead, opts.package, opts.activity, opts.timeout,
-             opts.simulate, opts.debug)
+  return run(args.readahead, args.package, args.activity, args.timeout,
+             args.simulate, args.debug)
+
+def get_args_from_opts(opts: argparse.Namespace) -> RunCommandArgs:
+  kwargs = {}
+  for field in RunCommandArgs._fields:
+    kwargs[field] = getattr(opts, field)
+  return RunCommandArgs(**kwargs)
 
 def main():
-  args = parse_options()
+  opts = parse_options()
+  args = get_args_from_opts(opts)
   result = run_test(args)
 
   if result is None:
diff --git a/startop/scripts/lib/logcat_utils.py b/startop/scripts/lib/logcat_utils.py
index 13b1c3a..8a3d00b 100644
--- a/startop/scripts/lib/logcat_utils.py
+++ b/startop/scripts/lib/logcat_utils.py
@@ -22,7 +22,7 @@
 from typing import Optional, Pattern
 
 # local import
-import print_utils
+import lib.print_utils as print_utils
 
 def parse_logcat_datetime(timestamp: str) -> Optional[datetime]:
   """Parses the timestamp of logcat.
@@ -61,7 +61,7 @@
                                             pattern: Pattern,
                                             timeout: datetime) -> Optional[str]:
   # Show the year in the timestampe.
-  logcat_cmd = 'adb logcat -v year -v threadtime -T'.split()
+  logcat_cmd = 'adb logcat -v UTC -v year -v threadtime -T'.split()
   logcat_cmd.append(str(timestamp))
   print_utils.debug_print('[LOGCAT]:' + ' '.join(logcat_cmd))
 
diff --git a/startop/scripts/lib/print_utils.py b/startop/scripts/lib/print_utils.py
index c33e0f9..8c5999d 100644
--- a/startop/scripts/lib/print_utils.py
+++ b/startop/scripts/lib/print_utils.py
@@ -27,3 +27,41 @@
 
 def error_print(*args, **kwargs):
   print('[ERROR]:', *args, file=sys.stderr, **kwargs)
+
+def _expand_gen_repr(args):
+  """Like repr but any generator-like object has its iterator consumed
+  and then called repr on."""
+  new_args_list = []
+  for i in args:
+    # detect iterable objects that do not have their own override of __str__
+    if hasattr(i, '__iter__'):
+      to_str = getattr(i, '__str__')
+      if to_str.__objclass__ == object:
+        # the repr for a generator is just type+address, expand it out instead.
+        new_args_list.append([_expand_gen_repr([j])[0] for j in i])
+        continue
+    # normal case: uses the built-in to-string
+    new_args_list.append(i)
+  return new_args_list
+
+def debug_print_gen(*args, **kwargs):
+  """Like _debug_print but will turn any iterable args into a list."""
+  if not DEBUG:
+    return
+
+  new_args_list = _expand_gen_repr(args)
+  debug_print(*new_args_list, **kwargs)
+
+def debug_print_nd(*args, **kwargs):
+  """Like _debug_print but will turn any NamedTuple-type args into a string."""
+  if not DEBUG:
+    return
+
+  new_args_list = []
+  for i in args:
+    if hasattr(i, '_field_types'):
+      new_args_list.append("%s: %s" % (i.__name__, i._field_types))
+    else:
+      new_args_list.append(i)
+
+  debug_print(*new_args_list, **kwargs)
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 484fd3b..2822fcc 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -2116,29 +2116,36 @@
     }
 
     /**
+     * TODO(b/137102918) Make this static, tests use this as an instance method currently.
+     *
      * @return the list of subId's that are active,
      *         is never null but the length maybe 0.
      * @hide
      */
     @UnsupportedAppUsage
     public @NonNull int[] getActiveSubscriptionIdList() {
-        int[] subId = null;
+        return getActiveSubscriptionIdList(/* visibleOnly */ true);
+    }
 
+    /**
+     * TODO(b/137102918) Make this static, tests use this as an instance method currently.
+     *
+     * @return a non-null list of subId's that are active.
+     *
+     * @hide
+     */
+    public @NonNull int[] getActiveSubscriptionIdList(boolean visibleOnly) {
         try {
             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
             if (iSub != null) {
-                subId = iSub.getActiveSubIdList(/*visibleOnly*/true);
+                int[] subId = iSub.getActiveSubIdList(visibleOnly);
+                if (subId != null) return subId;
             }
         } catch (RemoteException ex) {
             // ignore it
         }
 
-        if (subId == null) {
-            subId = new int[0];
-        }
-
-        return subId;
-
+        return new int[0];
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ed70a81e0..fd47561 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3983,9 +3983,14 @@
      * The returned set of subscriber IDs will include the subscriber ID corresponding to this
      * TelephonyManager's subId.
      *
+     * This is deprecated and {@link #getMergedSubscriberIdsFromGroup()} should be used for data
+     * usage merging purpose.
+     * TODO: remove this API.
+     *
      * @hide
      */
     @UnsupportedAppUsage
+    @Deprecated
     public @Nullable String[] getMergedSubscriberIds() {
         try {
             ITelephony telephony = getITelephony();
@@ -3998,6 +4003,28 @@
     }
 
     /**
+     * Return the set of subscriber IDs that should be considered "merged together" for data usage
+     * purposes. Unlike {@link #getMergedSubscriberIds()} this API merge subscriberIds based on
+     * subscription grouping: subscriberId of those in the same group will all be returned.
+     *
+     * <p>Requires the calling app to have READ_PRIVILEGED_PHONE_STATE permission.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @Nullable String[] getMergedSubscriberIdsFromGroup() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getMergedSubscriberIdsFromGroup(getSubId(), getOpPackageName());
+            }
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return null;
+    }
+
+    /**
      * Returns the MSISDN string.
      * for a GSM phone. Return null if it is unavailable.
      *
@@ -4901,7 +4928,8 @@
             ITelephony telephony = getITelephony();
             if (telephony == null)
                 return DATA_ACTIVITY_NONE;
-            return telephony.getDataActivity();
+            return telephony.getDataActivityForSubId(
+                    getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return DATA_ACTIVITY_NONE;
@@ -4949,7 +4977,8 @@
             ITelephony telephony = getITelephony();
             if (telephony == null)
                 return DATA_DISCONNECTED;
-            return telephony.getDataState();
+            return telephony.getDataStateForSubId(
+                    getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return DATA_DISCONNECTED;
diff --git a/telephony/java/android/telephony/ims/RcsControllerCall.java b/telephony/java/android/telephony/ims/RcsControllerCall.java
index a2d68ad..ce03c3c 100644
--- a/telephony/java/android/telephony/ims/RcsControllerCall.java
+++ b/telephony/java/android/telephony/ims/RcsControllerCall.java
@@ -19,10 +19,11 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.telephony.ims.aidl.IRcs;
+import android.telephony.ims.aidl.IRcsMessage;
 
 /**
- * A wrapper class around RPC calls that {@link RcsMessageStore} APIs to minimize boilerplate code.
+ * A wrapper class around RPC calls that {@link RcsMessageManager} APIs to minimize boilerplate
+ * code.
  *
  * @hide - not meant for public use
  */
@@ -34,13 +35,14 @@
     }
 
     <R> R call(RcsServiceCall<R> serviceCall) throws RcsMessageStoreException {
-        IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_RCS_SERVICE));
-        if (iRcs == null) {
+        IRcsMessage iRcsMessage = IRcsMessage.Stub.asInterface(ServiceManager.getService(
+                Context.TELEPHONY_RCS_MESSAGE_SERVICE));
+        if (iRcsMessage == null) {
             throw new RcsMessageStoreException("Could not connect to RCS storage service");
         }
 
         try {
-            return serviceCall.methodOnIRcs(iRcs, mContext.getOpPackageName());
+            return serviceCall.methodOnIRcs(iRcsMessage, mContext.getOpPackageName());
         } catch (RemoteException exception) {
             throw new RcsMessageStoreException(exception.getMessage());
         }
@@ -48,17 +50,17 @@
 
     void callWithNoReturn(RcsServiceCallWithNoReturn serviceCall)
             throws RcsMessageStoreException {
-        call((iRcs, callingPackage) -> {
-            serviceCall.methodOnIRcs(iRcs, callingPackage);
+        call((iRcsMessage, callingPackage) -> {
+            serviceCall.methodOnIRcs(iRcsMessage, callingPackage);
             return null;
         });
     }
 
     interface RcsServiceCall<R> {
-        R methodOnIRcs(IRcs iRcs, String callingPackage) throws RemoteException;
+        R methodOnIRcs(IRcsMessage iRcs, String callingPackage) throws RemoteException;
     }
 
     interface RcsServiceCallWithNoReturn {
-        void methodOnIRcs(IRcs iRcs, String callingPackage) throws RemoteException;
+        void methodOnIRcs(IRcsMessage iRcs, String callingPackage) throws RemoteException;
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsManager.java b/telephony/java/android/telephony/ims/RcsManager.java
deleted file mode 100644
index 0d6ca3c..0000000
--- a/telephony/java/android/telephony/ims/RcsManager.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2019 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.telephony.ims;
-
-import android.annotation.SystemService;
-import android.content.Context;
-
-/**
- * The manager class for RCS related utilities.
- *
- * @hide
- */
-@SystemService(Context.TELEPHONY_RCS_SERVICE)
-public class RcsManager {
-    private final RcsMessageStore mRcsMessageStore;
-
-    /**
-     * @hide
-     */
-    public RcsManager(Context context) {
-        mRcsMessageStore = new RcsMessageStore(context);
-    }
-
-    /**
-     * Returns an instance of {@link RcsMessageStore}
-     */
-    public RcsMessageStore getRcsMessageStore() {
-        return mRcsMessageStore;
-    }
-}
diff --git a/telephony/java/android/telephony/ims/RcsMessageStore.java b/telephony/java/android/telephony/ims/RcsMessageManager.java
similarity index 96%
rename from telephony/java/android/telephony/ims/RcsMessageStore.java
rename to telephony/java/android/telephony/ims/RcsMessageManager.java
index d112798..a1c7c0f 100644
--- a/telephony/java/android/telephony/ims/RcsMessageStore.java
+++ b/telephony/java/android/telephony/ims/RcsMessageManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemService;
 import android.annotation.WorkerThread;
 import android.content.Context;
 import android.net.Uri;
@@ -25,15 +26,20 @@
 import java.util.List;
 
 /**
- * RcsMessageStore is the application interface to RcsProvider and provides access methods to
+ * RcsMessageManager is the application interface to RcsProvider and provides access methods to
  * RCS related database tables.
  *
  * @hide
  */
-public class RcsMessageStore {
+@SystemService(Context.TELEPHONY_RCS_MESSAGE_SERVICE)
+public class RcsMessageManager {
     RcsControllerCall mRcsControllerCall;
 
-    RcsMessageStore(Context context) {
+    /**
+     * Use {@link Context#getSystemService(String)} to get an instance of this service.
+     * @hide
+     */
+    public RcsMessageManager(Context context) {
         mRcsControllerCall = new RcsControllerCall(context);
     }
 
diff --git a/telephony/java/android/telephony/ims/aidl/IRcs.aidl b/telephony/java/android/telephony/ims/aidl/IRcsMessage.aidl
similarity index 99%
rename from telephony/java/android/telephony/ims/aidl/IRcs.aidl
rename to telephony/java/android/telephony/ims/aidl/IRcsMessage.aidl
index 9ee15da..0ae6303 100644
--- a/telephony/java/android/telephony/ims/aidl/IRcs.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IRcsMessage.aidl
@@ -35,7 +35,7 @@
  * RPC definition between RCS storage APIs and phone process.
  * {@hide}
  */
-interface IRcs {
+interface IRcsMessage {
     /////////////////////////
     // RcsMessageStore APIs
     /////////////////////////
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 5a27a0f..dcfd193 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -308,18 +308,46 @@
      */
     List<NeighboringCellInfo> getNeighboringCellInfo(String callingPkg);
 
-     @UnsupportedAppUsage
-     int getCallState();
+    @UnsupportedAppUsage
+    int getCallState();
 
     /**
      * Returns the call state for a slot.
      */
-     int getCallStateForSlot(int slotIndex);
+    int getCallStateForSlot(int slotIndex);
 
-     @UnsupportedAppUsage
-     int getDataActivity();
-     @UnsupportedAppUsage
-     int getDataState();
+    /**
+     * Replaced by getDataActivityForSubId.
+     */
+    int getDataActivity();
+
+    /**
+     * Returns a constant indicating the type of activity on a data connection
+     * (cellular).
+     *
+     * @see #DATA_ACTIVITY_NONE
+     * @see #DATA_ACTIVITY_IN
+     * @see #DATA_ACTIVITY_OUT
+     * @see #DATA_ACTIVITY_INOUT
+     * @see #DATA_ACTIVITY_DORMANT
+     */
+    int getDataActivityForSubId(int subId);
+
+    /**
+     * Replaced by getDataStateForSubId.
+     */
+    int getDataState();
+
+    /**
+     * Returns a constant indicating the current data connection state
+     * (cellular).
+     *
+     * @see #DATA_DISCONNECTED
+     * @see #DATA_CONNECTING
+     * @see #DATA_CONNECTED
+     * @see #DATA_SUSPENDED
+     */
+    int getDataStateForSubId(int subId);
 
     /**
      * Returns the current active phone type as integer.
@@ -1059,6 +1087,11 @@
     String[] getMergedSubscriberIds(int subId, String callingPackage);
 
     /**
+     * @hide
+     */
+    String[] getMergedSubscriberIdsFromGroup(int subId, String callingPackage);
+
+    /**
      * Override the operator branding for the current ICCID.
      *
      * Once set, whenever the SIM is present in the device, the service
diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
index 7a0ab9c..51c5d12 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
@@ -600,26 +600,21 @@
         }
     }
 
-    /**
-     * Returns whether the provided uid has carrier privileges for any active subscription ID.
-     */
-    private static boolean checkCarrierPrivilegeForAnySubId(Context context,
-            Supplier<ITelephony> telephonySupplier, int uid) {
+    /** Returns whether the provided uid has carrier privileges for any active subscription ID. */
+    private static boolean checkCarrierPrivilegeForAnySubId(
+            Context context, Supplier<ITelephony> telephonySupplier, int uid) {
         SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
                 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
-        int[] activeSubIds = sm.getActiveSubscriptionIdList();
-        if (activeSubIds != null) {
-            for (int activeSubId : activeSubIds) {
-                if (getCarrierPrivilegeStatus(telephonySupplier, activeSubId, uid)
-                        == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
-                    return true;
-                }
+        int[] activeSubIds = sm.getActiveSubscriptionIdList(/* visibleOnly */ false);
+        for (int activeSubId : activeSubIds) {
+            if (getCarrierPrivilegeStatus(telephonySupplier, activeSubId, uid)
+                    == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+                return true;
             }
         }
         return false;
     }
 
-
     private static int getCarrierPrivilegeStatus(
             Supplier<ITelephony> telephonySupplier, int subId, int uid) {
         ITelephony telephony = telephonySupplier.get();
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java
index c21c403..3415d2e 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java
@@ -384,55 +384,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readInt(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readLong(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java
index 09fe40e..8796807 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java
@@ -306,55 +306,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readInt(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readLong(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java
index 118fe34..2b54e96 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java
@@ -611,55 +611,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readInt(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readLong(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java
index f55d951..19bad70 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java
@@ -454,55 +454,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readLong(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java
index df68476..2bc61a0 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java
@@ -431,55 +431,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readLong(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java
index af4130b..a54ecf9 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java
@@ -532,55 +532,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readInt(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java
index 9bc07dc..0477e9e 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java
@@ -563,55 +563,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readInt(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readLong(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java
index 0065870..a7f3f65 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java
@@ -449,55 +449,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readLong(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java
index 4d6d105..dc42468 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java
@@ -529,55 +529,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readInt(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java
index 5e49eea..1c0832e 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java
@@ -391,55 +391,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readInt(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readLong(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java
index 75c88a4..d349ea2 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java
@@ -431,55 +431,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readLong(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java
index 4c65cf4..81a9c59 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java
@@ -531,55 +531,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readInt(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java
index 6854cd8..9719444 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java
@@ -431,55 +431,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readLong(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java
index c53e9d7..118476c 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java
@@ -506,55 +506,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readInt(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java
index 816d5f9..51ee78f 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java
@@ -287,55 +287,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readInt(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readLong(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java
index 50fc537..42f3e99 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java
@@ -448,55 +448,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readLong(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java
index 20969e9..8ba2c0c 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java
@@ -525,55 +525,55 @@
         };
 
         ProtoInputStream pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readFloat(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readDouble(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readInt(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBoolean(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readBytes(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
 
         pi = new ProtoInputStream(protobuf);
-        pi.isNextField(fieldId1);
+        pi.nextField();
         try {
             pi.readString(fieldId1);
-            fail("Should have throw IllegalArgumentException");
+            fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException iae) {
             // good
         }
diff --git a/tools/preload2/Android.bp b/tools/preload2/Android.bp
new file mode 100644
index 0000000..5809421
--- /dev/null
+++ b/tools/preload2/Android.bp
@@ -0,0 +1,50 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_library_host {
+    name: "preload2",
+
+    srcs: ["src/**/*.java"],
+
+    // To connect to devices (and take hprof dumps).
+    static_libs: [
+        "ddmlib-prebuilt",
+        "tools-common-prebuilt",
+
+        // To process hprof dumps.
+        "perflib-prebuilt",
+
+        "trove-prebuilt",
+        "guavalib",
+
+        // For JDWP access we use the framework in the JDWP tests from Apache Harmony, for
+        // convenience (and to not depend on internal JDK APIs).
+        "apache-harmony-jdwp-tests",
+        "junit",
+    ],
+
+    // Copy to build artifacts
+    dist: {
+        targets: [
+            "dist_files",
+        ],
+    },
+}
+
+// Copy the preload-tool shell script to the host's bin directory.
+sh_binary_host {
+    name: "preload-tool",
+    src: "preload-tool",
+    required: ["preload2"],
+}
diff --git a/tools/preload2/Android.mk b/tools/preload2/Android.mk
deleted file mode 100644
index d3ee1d3..0000000
--- a/tools/preload2/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-
-# To connect to devices (and take hprof dumps).
-LOCAL_STATIC_JAVA_LIBRARIES := ddmlib-prebuilt tools-common-prebuilt
-
-# To process hprof dumps.
-LOCAL_STATIC_JAVA_LIBRARIES += perflib-prebuilt trove-prebuilt guavalib
-
-# For JDWP access we use the framework in the JDWP tests from Apache Harmony, for
-# convenience (and to not depend on internal JDK APIs).
-LOCAL_STATIC_JAVA_LIBRARIES += apache-harmony-jdwp-tests-host junit-host
-
-LOCAL_MODULE:= preload2
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-# Copy to build artifacts
-$(call dist-for-goals,dist_files,$(LOCAL_BUILT_MODULE):$(LOCAL_MODULE).jar)
-
-# Copy the preload-tool shell script to the host's bin directory.
-include $(CLEAR_VARS)
-LOCAL_IS_HOST_MODULE := true
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_MODULE := preload-tool
-LOCAL_SRC_FILES := preload-tool
-LOCAL_REQUIRED_MODULES := preload2
-include $(BUILD_PREBUILT)
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 9010f3c9..1829baa 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -29,7 +29,6 @@
 import android.net.wifi.ISoftApCallback;
 import android.net.wifi.ITrafficStateCallback;
 import android.net.wifi.IOnWifiUsabilityStatsListener;
-import android.net.wifi.PasspointManagementObjectDefinition;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiActivityEnergyInfo;
 import android.net.wifi.WifiConfiguration;
diff --git a/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl b/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl
deleted file mode 100644
index eb7cc39..0000000
--- a/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (c) 2008, 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.wifi;
-
-parcelable PasspointManagementObjectDefinition;
diff --git a/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java b/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java
deleted file mode 100644
index 70577b9..0000000
--- a/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2016 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.wifi;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * This object describes a partial tree structure in the Hotspot 2.0 release 2 management object.
- * The object is used during subscription remediation to modify parts of an existing PPS MO
- * tree (Hotspot 2.0 specification section 9.1).
- * @hide
- */
-public class PasspointManagementObjectDefinition implements Parcelable {
-    private final String mBaseUri;
-    private final String mUrn;
-    private final String mMoTree;
-
-    public PasspointManagementObjectDefinition(String baseUri, String urn, String moTree) {
-        mBaseUri = baseUri;
-        mUrn = urn;
-        mMoTree = moTree;
-    }
-
-    public String getBaseUri() {
-        return mBaseUri;
-    }
-
-    public String getUrn() {
-        return mUrn;
-    }
-
-    public String getMoTree() {
-        return mMoTree;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(mBaseUri);
-        dest.writeString(mUrn);
-        dest.writeString(mMoTree);
-    }
-
-    /**
-     * Implement the Parcelable interface {@hide}
-     */
-    public static final @android.annotation.NonNull Creator<PasspointManagementObjectDefinition> CREATOR =
-            new Creator<PasspointManagementObjectDefinition>() {
-                public PasspointManagementObjectDefinition createFromParcel(Parcel in) {
-                    return new PasspointManagementObjectDefinition(
-                            in.readString(),    /* base URI */
-                            in.readString(),    /* URN */
-                            in.readString()     /* Tree XML */
-                    );
-                }
-
-                public PasspointManagementObjectDefinition[] newArray(int size) {
-                    return new PasspointManagementObjectDefinition[size];
-                }
-            };
-}
-
diff --git a/wifi/java/android/net/wifi/WpsResult.aidl b/wifi/java/android/net/wifi/WpsResult.aidl
deleted file mode 100644
index eb4c4f5..0000000
--- a/wifi/java/android/net/wifi/WpsResult.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (c) 2010, 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.wifi;
-
-parcelable WpsResult;
diff --git a/wifi/java/android/net/wifi/WpsResult.java b/wifi/java/android/net/wifi/WpsResult.java
deleted file mode 100644
index f2ffb6f..0000000
--- a/wifi/java/android/net/wifi/WpsResult.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2010 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.wifi;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * A class representing the result of a WPS request
- * @hide
- */
-public class WpsResult implements Parcelable {
-
-    public enum Status {
-        SUCCESS,
-        FAILURE,
-        IN_PROGRESS,
-    }
-
-    public Status status;
-
-    public String pin;
-
-    public WpsResult() {
-        status = Status.FAILURE;
-        pin = null;
-    }
-
-    public WpsResult(Status s) {
-        status = s;
-        pin = null;
-    }
-
-    public String toString() {
-        StringBuffer sbuf = new StringBuffer();
-        sbuf.append(" status: ").append(status.toString());
-        sbuf.append('\n');
-        sbuf.append(" pin: ").append(pin);
-        sbuf.append("\n");
-        return sbuf.toString();
-    }
-
-    /** Implement the Parcelable interface {@hide} */
-    public int describeContents() {
-        return 0;
-    }
-
-    /** copy constructor {@hide} */
-    public WpsResult(WpsResult source) {
-        if (source != null) {
-            status = source.status;
-            pin = source.pin;
-        }
-    }
-
-    /** Implement the Parcelable interface {@hide} */
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(status.name());
-        dest.writeString(pin);
-    }
-
-    /** Implement the Parcelable interface {@hide} */
-    public static final @android.annotation.NonNull Creator<WpsResult> CREATOR =
-        new Creator<WpsResult>() {
-            public WpsResult createFromParcel(Parcel in) {
-                WpsResult result = new WpsResult();
-                result.status = Status.valueOf(in.readString());
-                result.pin = in.readString();
-                return result;
-            }
-
-            public WpsResult[] newArray(int size) {
-                return new WpsResult[size];
-            }
-        };
-}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl b/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
deleted file mode 100644
index e923f1f..0000000
--- a/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * 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.wifi.hotspot2.pps;
-
-parcelable Policy;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
deleted file mode 100644
index 701db47..0000000
--- a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * 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.wifi.hotspot2.pps;
-
-parcelable UpdateParameter;
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.aidl b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.aidl
deleted file mode 100644
index 3d8a476..0000000
--- a/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (c) 2012, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.p2p;
-
-parcelable WifiP2pGroupList;
\ No newline at end of file
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl
deleted file mode 100644
index c81d1f9..0000000
--- a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.p2p.servicediscovery;
-
-parcelable WifiP2pServiceResponse;