Merge "Add DelegatingTransport which delegates all transport methods to its delegate"
diff --git a/Android.bp b/Android.bp
index bef3251..d5fc957 100644
--- a/Android.bp
+++ b/Android.bp
@@ -899,8 +899,10 @@
 
 packages_to_document = [
     "android",
+    "dalvik",
     "java",
     "javax",
+    "junit",
     "org.apache.http",
     "org.json",
     "org.w3c.dom",
@@ -931,7 +933,7 @@
         "sdk-dir",
         "api-versions-jars-dir",
     ],
-    previous_api: ":last-released-public-api",
+    previous_api: ":last-released-public-api-for-metalava-annotations",
     merge_annotations_dirs: [
         "metalava-manual",
         "ojluni-annotated-sdk-stubs",
@@ -988,7 +990,7 @@
     local_sourcepaths: frameworks_base_subdirs,
     installable: false,
     annotations_enabled: true,
-    previous_api: ":last-released-public-api",
+    previous_api: ":last-released-public-api-for-metalava-annotations",
     merge_annotations_dirs: [
         "metalava-manual",
         "ojluni-annotated-sdk-stubs",
@@ -1324,7 +1326,7 @@
     installable: false,
     sdk_version: "core_platform",
     annotations_enabled: true,
-    previous_api: ":last-released-public-api",
+    previous_api: ":last-released-public-api-for-metalava-annotations",
     merge_annotations_dirs: [
         "metalava-manual",
         "ojluni-annotated-sdk-stubs",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index a633350..329d4b7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -56,8 +56,6 @@
 import android.os.BatteryStatsInternal;
 import android.os.Binder;
 import android.os.Handler;
-import android.os.IThermalService;
-import android.os.IThermalStatusListener;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
@@ -66,7 +64,6 @@
 import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.SystemClock;
-import android.os.Temperature;
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
 import android.os.WorkSource;
@@ -81,7 +78,6 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.ArrayUtils;
@@ -105,6 +101,8 @@
 import com.android.server.job.controllers.StateController;
 import com.android.server.job.controllers.StorageController;
 import com.android.server.job.controllers.TimeController;
+import com.android.server.job.restrictions.JobRestriction;
+import com.android.server.job.restrictions.ThermalStatusRestriction;
 
 import libcore.util.EmptyArray;
 
@@ -186,12 +184,12 @@
     private final DeviceIdleJobsController mDeviceIdleJobsController;
     /** Needed to get remaining quota time. */
     private final QuotaController mQuotaController;
-
-    /** Need directly for receiving thermal events */
-    private IThermalService mThermalService;
-    /** Thermal constraint. */
-    @GuardedBy("mLock")
-    private boolean mThermalConstraint = false;
+    /**
+     * List of restrictions.
+     * Note: do not add to or remove from this list at runtime except in the constructor, because we
+     * do not synchronize access to this list.
+     */
+    private final List<JobRestriction> mJobRestrictions;
 
     /**
      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
@@ -285,19 +283,6 @@
         }
     }
 
-    /**
-     *  Thermal event received from Thermal Service
-     */
-    private final class ThermalStatusListener extends IThermalStatusListener.Stub {
-        @Override public void onStatusChange(int status) {
-            // Throttle for Temperature.THROTTLING_SEVERE and above
-            synchronized (mLock) {
-                mThermalConstraint = status >= Temperature.THROTTLING_SEVERE;
-            }
-            onControllerStateChanged();
-        }
-    }
-
     static class MaxJobCounts {
         private final KeyValueListParser.IntValue mTotal;
         private final KeyValueListParser.IntValue mMaxBg;
@@ -1292,6 +1277,10 @@
         mQuotaController = new QuotaController(this);
         mControllers.add(mQuotaController);
 
+        // Create restrictions
+        mJobRestrictions = new ArrayList<>();
+        mJobRestrictions.add(new ThermalStatusRestriction(this));
+
         // If the job store determined that it can't yet reschedule persisted jobs,
         // we need to start watching the clock.
         if (!mJobs.jobTimesInflatedValid()) {
@@ -1383,15 +1372,9 @@
 
             // Remove any jobs that are not associated with any of the current users.
             cancelJobsForNonExistentUsers();
-            // Register thermal callback
-            mThermalService = IThermalService.Stub.asInterface(
-                    ServiceManager.getService(Context.THERMAL_SERVICE));
-            if (mThermalService != null) {
-                try {
-                    mThermalService.registerThermalStatusListener(new ThermalStatusListener());
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Failed to register thermal callback.", e);
-                }
+
+            for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
+                mJobRestrictions.get(i).onSystemServicesReady();
             }
         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
             synchronized (mLock) {
@@ -1833,9 +1816,28 @@
         }
     }
 
-    private boolean isJobThermalConstrainedLocked(JobStatus job) {
-        return mThermalConstraint && job.hasConnectivityConstraint()
-                && (evaluateJobPriorityLocked(job) < JobInfo.PRIORITY_FOREGROUND_APP);
+    /**
+     * Check if a job is restricted by any of the declared {@link JobRestriction}s.
+     * Note, that the jobs with {@link JobInfo#PRIORITY_FOREGROUND_APP} priority or higher may not
+     * be restricted, thus we won't even perform the check, but simply return null early.
+     *
+     * @param job to be checked
+     * @return the first {@link JobRestriction} restricting the given job that has been found; null
+     * - if passes all the restrictions or has priority {@link JobInfo#PRIORITY_FOREGROUND_APP}
+     * or higher.
+     */
+    private JobRestriction checkIfRestricted(JobStatus job) {
+        if (evaluateJobPriorityLocked(job) >= JobInfo.PRIORITY_FOREGROUND_APP) {
+            // Jobs with PRIORITY_FOREGROUND_APP or higher should not be restricted
+            return null;
+        }
+        for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
+            final JobRestriction restriction = mJobRestrictions.get(i);
+            if (restriction.isJobRestricted(job)) {
+                return restriction;
+            }
+        }
+        return null;
     }
 
     private void stopNonReadyActiveJobsLocked() {
@@ -1849,10 +1851,13 @@
                 serviceContext.cancelExecutingJobLocked(
                         JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
                         "cancelled due to unsatisfied constraints");
-            } else if (isJobThermalConstrainedLocked(running)) {
-                serviceContext.cancelExecutingJobLocked(
-                        JobParameters.REASON_DEVICE_THERMAL,
-                        "cancelled due to thermal condition");
+            } else {
+                final JobRestriction restriction = checkIfRestricted(running);
+                if (restriction != null) {
+                    final int reason = restriction.getReason();
+                    serviceContext.cancelExecutingJobLocked(reason,
+                            "restricted due to " + JobParameters.getReasonName(reason));
+                }
             }
         }
     }
@@ -2089,7 +2094,7 @@
             return false;
         }
 
-        if (isJobThermalConstrainedLocked(job)) {
+        if (checkIfRestricted(job) != null) {
             return false;
         }
 
@@ -2170,7 +2175,7 @@
             return false;
         }
 
-        if (isJobThermalConstrainedLocked(job)) {
+        if (checkIfRestricted(job) != null) {
             return false;
         }
 
@@ -2982,9 +2987,12 @@
             pw.print("    In parole?: ");
             pw.print(mInParole);
             pw.println();
-            pw.print("    In thermal throttling?: ");
-            pw.print(mThermalConstraint);
-            pw.println();
+
+            for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
+                pw.print("    ");
+                mJobRestrictions.get(i).dumpConstants(pw);
+                pw.println();
+            }
             pw.println();
 
             pw.println("Started users: " + Arrays.toString(mStartedUsers));
@@ -3005,14 +3013,30 @@
 
                     job.dump(pw, "    ", true, nowElapsed);
 
+
+                    pw.print("    Restricted due to:");
+                    final boolean isRestricted = checkIfRestricted(job) != null;
+                    if (isRestricted) {
+                        for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
+                            final JobRestriction restriction = mJobRestrictions.get(i);
+                            if (restriction.isJobRestricted(job)) {
+                                final int reason = restriction.getReason();
+                                pw.write(" " + JobParameters.getReasonName(reason) + "[" + reason + "]");
+                            }
+                        }
+                    } else {
+                        pw.print(" none");
+                    }
+                    pw.println(".");
+
                     pw.print("    Ready: ");
                     pw.print(isReadyToBeExecutedLocked(job));
                     pw.print(" (job=");
                     pw.print(job.isReady());
                     pw.print(" user=");
                     pw.print(areUsersStartedLocked(job));
-                    pw.print(" !thermal=");
-                    pw.print(!isJobThermalConstrainedLocked(job));
+                    pw.print(" !restricted=");
+                    pw.print(!isRestricted);
                     pw.print(" !pending=");
                     pw.print(!mPendingJobs.contains(job));
                     pw.print(" !active=");
@@ -3152,7 +3176,9 @@
             proto.end(settingsToken);
 
             proto.write(JobSchedulerServiceDumpProto.IN_PAROLE, mInParole);
-            proto.write(JobSchedulerServiceDumpProto.IN_THERMAL, mThermalConstraint);
+            for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
+                mJobRestrictions.get(i).dumpConstants(proto);
+            }
 
             for (int u : mStartedUsers) {
                 proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u);
@@ -3179,8 +3205,8 @@
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.ARE_USERS_STARTED,
                             areUsersStartedLocked(job));
                     proto.write(
-                            JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_THERMAL_CONSTRAINED,
-                            isJobThermalConstrainedLocked(job));
+                            JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_RESTRICTED,
+                            checkIfRestricted(job) != null);
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_PENDING,
                             mPendingJobs.contains(job));
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_CURRENTLY_ACTIVE,
@@ -3190,6 +3216,16 @@
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_COMPONENT_USABLE,
                             isComponentUsable(job));
 
+                    for (JobRestriction restriction : mJobRestrictions) {
+                        final long restrictionsToken = proto.start(
+                                JobSchedulerServiceDumpProto.RegisteredJob.RESTRICTIONS);
+                        proto.write(JobSchedulerServiceDumpProto.JobRestriction.REASON,
+                                restriction.getReason());
+                        proto.write(JobSchedulerServiceDumpProto.JobRestriction.IS_RESTRICTING,
+                                restriction.isJobRestricted(job));
+                        proto.end(restrictionsToken);
+                    }
+
                     proto.end(rjToken);
                 }
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
new file mode 100644
index 0000000..e180c55
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
@@ -0,0 +1,72 @@
+/*
+ * 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.job.restrictions;
+
+import android.app.job.JobInfo;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.controllers.JobStatus;
+
+/**
+ * Used by {@link JobSchedulerService} to impose additional restrictions regarding whether jobs
+ * should be scheduled or not based on the state of the system/device.
+ * Every restriction is associated with exactly one reason (from {@link
+ * android.app.job.JobParameters#JOB_STOP_REASON_CODES}), which could be retrieved using {@link
+ * #getReason()}.
+ * Note, that this is not taken into account for the jobs that have priority
+ * {@link JobInfo#PRIORITY_FOREGROUND_APP} or higher.
+ */
+public abstract class JobRestriction {
+
+    final JobSchedulerService mService;
+    private final int mReason;
+
+    JobRestriction(JobSchedulerService service, int reason) {
+        mService = service;
+        mReason = reason;
+    }
+
+    /**
+     * Called when the system boot phase has reached
+     * {@link com.android.server.SystemService#PHASE_SYSTEM_SERVICES_READY}.
+     */
+    public void onSystemServicesReady() {
+    }
+
+    /**
+     * Called by {@link JobSchedulerService} to check if it may proceed with scheduling the job (in
+     * case all constraints are satisfied and all other {@link JobRestriction}s are fine with it)
+     *
+     * @param job to be checked
+     * @return false if the {@link JobSchedulerService} should not schedule this job at the moment,
+     * true - otherwise
+     */
+    public abstract boolean isJobRestricted(JobStatus job);
+
+    /** Dump any internal constants the Restriction may have. */
+    public abstract void dumpConstants(IndentingPrintWriter pw);
+
+    /** Dump any internal constants the Restriction may have. */
+    public abstract void dumpConstants(ProtoOutputStream proto);
+
+    /** @return reason code for the Restriction. */
+    public final int getReason() {
+        return mReason;
+    }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
new file mode 100644
index 0000000..b97da59
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -0,0 +1,81 @@
+/*
+ * 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.job.restrictions;
+
+import android.app.job.JobParameters;
+import android.content.Context;
+import android.os.IThermalService;
+import android.os.IThermalStatusListener;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Temperature;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobSchedulerServiceDumpProto;
+import com.android.server.job.controllers.JobStatus;
+
+public class ThermalStatusRestriction extends JobRestriction {
+    private static final String TAG = "ThermalStatusRestriction";
+
+    private volatile boolean mIsThermalRestricted = false;
+
+    public ThermalStatusRestriction(JobSchedulerService service) {
+        super(service, JobParameters.REASON_DEVICE_THERMAL);
+    }
+
+    @Override
+    public void onSystemServicesReady() {
+        final IThermalService thermalService = IThermalService.Stub.asInterface(
+                ServiceManager.getService(Context.THERMAL_SERVICE));
+        if (thermalService != null) {
+            try {
+                thermalService.registerThermalStatusListener(new IThermalStatusListener.Stub() {
+                    @Override
+                    public void onStatusChange(int status) {
+                        final boolean shouldBeActive = status >= Temperature.THROTTLING_SEVERE;
+                        if (mIsThermalRestricted == shouldBeActive) {
+                            return;
+                        }
+                        mIsThermalRestricted = shouldBeActive;
+                        mService.onControllerStateChanged();
+                    }
+                });
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to register thermal callback.", e);
+            }
+        }
+    }
+
+    @Override
+    public boolean isJobRestricted(JobStatus job) {
+        return mIsThermalRestricted && job.hasConnectivityConstraint();
+    }
+
+    @Override
+    public void dumpConstants(IndentingPrintWriter pw) {
+        pw.print("In thermal throttling?: ");
+        pw.print(mIsThermalRestricted);
+    }
+
+    @Override
+    public void dumpConstants(ProtoOutputStream proto) {
+        proto.write(JobSchedulerServiceDumpProto.IN_THERMAL, mIsThermalRestricted);
+    }
+}
diff --git a/api/current.txt b/api/current.txt
index b4d110e..d688220 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -29463,7 +29463,8 @@
   }
 
   public class AudioGroup {
-    ctor public AudioGroup();
+    ctor @Deprecated public AudioGroup();
+    ctor public AudioGroup(@Nullable android.content.Context);
     method public void clear();
     method public int getMode();
     method public android.net.rtp.AudioStream[] getStreams();
diff --git a/api/system-current.txt b/api/system-current.txt
index 0d55b81..d31bfeb 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1663,7 +1663,8 @@
     field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
     field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000
     field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
-    field public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 8; // 0x8
+    field public static final int FLAG_PERMISSION_REVOKED_COMPAT = 8; // 0x8
+    field @Deprecated public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 8; // 0x8
     field public static final int FLAG_PERMISSION_SYSTEM_FIXED = 16; // 0x10
     field public static final int FLAG_PERMISSION_USER_FIXED = 2; // 0x2
     field public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 512; // 0x200
@@ -1731,7 +1732,7 @@
     method public void onPermissionsChanged(int);
   }
 
-  @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
+  @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
   }
 
   public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 34312f6..51b569f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -21,6 +21,10 @@
     field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
   }
 
+  public static final class Manifest.permission_group {
+    field public static final String UNDEFINED = "android.permission-group.UNDEFINED";
+  }
+
   public static final class R.bool {
     field public static final int config_perDisplayFocusEnabled = 17891332; // 0x1110004
   }
@@ -739,7 +743,8 @@
     field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
     field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000
     field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
-    field public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 8; // 0x8
+    field public static final int FLAG_PERMISSION_REVOKED_COMPAT = 8; // 0x8
+    field @Deprecated public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 8; // 0x8
     field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80
     field public static final int FLAG_PERMISSION_SYSTEM_FIXED = 16; // 0x10
     field public static final int FLAG_PERMISSION_USER_FIXED = 2; // 0x2
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index d4d5871..4c77ba4 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -69,6 +69,7 @@
             static_libs: [
                 "libandroidfw",
                 "libbase",
+                "libcutils",
                 "libutils",
                 "libziparchive",
             ],
@@ -121,6 +122,7 @@
             static_libs: [
                 "libandroidfw",
                 "libbase",
+                "libcutils",
                 "libidmap2",
                 "liblog",
                 "libutils",
@@ -163,6 +165,7 @@
             static_libs: [
                 "libandroidfw",
                 "libbase",
+                "libcutils",
                 "libidmap2",
                 "liblog",
                 "libutils",
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 43058d5..05ff490 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -99,6 +99,8 @@
         "src/shell/shell_config.proto",
         "src/shell/ShellSubscriber.cpp",
         "src/socket/StatsSocketListener.cpp",
+        "src/state/StateManager.cpp",
+        "src/state/StateTracker.cpp",
         "src/stats_log_util.cpp",
         "src/statscompanion_util.cpp",
         "src/statsd_config.proto",
@@ -253,6 +255,7 @@
         "tests/metrics/ValueMetricProducer_test.cpp",
         "tests/MetricsManager_test.cpp",
         "tests/shell/ShellSubscriber_test.cpp",
+        "tests/state/StateTracker_test.cpp",
         "tests/statsd_test_util.cpp",
         "tests/StatsLogProcessor_test.cpp",
         "tests/StatsService_test.cpp",
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index af8b3af..5e156bb 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -59,6 +59,16 @@
     return JenkinsHashWhiten(hash);
 }
 
+bool filterValues(const Matcher& matcherField, const vector<FieldValue>& values, Value* output) {
+    for (const auto& value : values) {
+        if (value.mField.matches(matcherField)) {
+            (*output) = value.mValue;
+            return true;
+        }
+    }
+    return false;
+}
+
 bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue>& values,
                   HashableDimensionKey* output) {
     size_t num_matches = 0;
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 6f4941f..a123850 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -120,6 +120,13 @@
 android::hash_t hashDimension(const HashableDimensionKey& key);
 
 /**
+ * Returns true if a FieldValue field matches the matcher field.
+ * The value of the FieldValue is output.
+ */
+bool filterValues(const Matcher& matcherField, const std::vector<FieldValue>& values,
+                  Value* output);
+
+/**
  * Creating HashableDimensionKeys from FieldValues using matcher.
  *
  * This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL
@@ -169,4 +176,4 @@
         return android::JenkinsHashWhiten(hash);
     }
 };
-}  // namespace std
\ No newline at end of file
+}  // namespace std
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 1c7180f..b665a8b 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -266,7 +266,9 @@
                     IResultReceiver::asInterface(data.readStrongBinder());
 
             err = command(in, out, err, args, resultReceiver);
-            resultReceiver->send(err);
+            if (resultReceiver != nullptr) {
+                resultReceiver->send(err);
+            }
             return NO_ERROR;
         }
         default: { return BnStatsManager::onTransact(code, data, reply, flags); }
@@ -411,13 +413,20 @@
             return cmd_trigger_active_config_broadcast(out, args);
         }
         if (!args[0].compare(String8("data-subscribe"))) {
-            if (mShellSubscriber == nullptr) {
-                mShellSubscriber = new ShellSubscriber(mUidMap, mPullerManager);
+            {
+                std::lock_guard<std::mutex> lock(mShellSubscriberMutex);
+                if (mShellSubscriber == nullptr) {
+                    mShellSubscriber = new ShellSubscriber(mUidMap, mPullerManager);
+                }
             }
             int timeoutSec = -1;
             if (argCount >= 2) {
                 timeoutSec = atoi(args[1].c_str());
             }
+            if (resultReceiver == nullptr) {
+                ALOGI("Null resultReceiver given, no subscription will be started");
+                return UNEXPECTED_NULL;
+            }
             mShellSubscriber->startNewSubscription(in, out, resultReceiver, timeoutSec);
             return NO_ERROR;
         }
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 53b6ce9..9490948 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -432,6 +432,10 @@
 
     sp<ShellSubscriber> mShellSubscriber;
 
+    /**
+     * Mutex for setting the shell subscriber
+     */
+    mutable mutex mShellSubscriberMutex;
     std::shared_ptr<LogEventQueue> mEventQueue;
 
     FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 78a6609..b71a86b 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -340,7 +340,7 @@
     }
 
     // Pulled events will start at field 10000.
-    // Next: 10064
+    // Next: 10065
     oneof pulled {
         WifiBytesTransfer wifi_bytes_transfer = 10000;
         WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -406,6 +406,7 @@
         ProcessSystemIonHeapSize process_system_ion_heap_size = 10061;
         SurfaceflingerStatsGlobalInfo surfaceflinger_stats_global_info = 10062;
         SurfaceflingerStatsLayerInfo surfaceflinger_stats_layer_info = 10063;
+        ProcessMemorySnapshot process_memory_snapshot = 10064;
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP.
@@ -3069,9 +3070,9 @@
  *     services/core/java/com/android/server/wm/Session.java
  */
 message OverlayStateChanged {
-    optional int32 uid = 1 [(is_uid) = true];
+    optional int32 uid = 1 [(state_field_option).option = PRIMARY, (is_uid) = true];
 
-    optional string package_name = 2;
+    optional string package_name = 2 [(state_field_option).option = PRIMARY];
 
     optional bool using_alert_window = 3;
 
@@ -3079,7 +3080,7 @@
         ENTERED = 1;
         EXITED = 2;
     }
-    optional State state = 4;
+    optional State state = 4 [(state_field_option).option = EXCLUSIVE];
 }
 
 /*
@@ -4108,6 +4109,46 @@
 }
 
 /*
+ * Logs the memory stats for a process.
+ *
+ * Pulled from StatsCompanionService for all managed processes (from ActivityManagerService)
+ * and for selected native processes.
+ */
+message ProcessMemorySnapshot {
+    // The uid if available. -1 means not available.
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // The process name.
+    // Usually package name or process cmdline.
+    // Provided by ActivityManagerService or read from /proc/PID/cmdline.
+    optional string process_name = 2;
+
+    // The pid of the process.
+    // Allows to disambiguate instances of the process.
+    optional int32 pid = 3;
+
+    // The current OOM score adjustment value.
+    // Read from ProcessRecord for managed processes.
+    // Placeholder -1001 (OOM_SCORE_ADJ_MIN - 1, outside of allowed range) for native ones.
+    optional int32 oom_score_adj = 4;
+
+    // The current RSS of the process.
+    // VmRSS from /proc/pid/status.
+    optional int32 rss_in_kilobytes = 5;
+
+    // The current anon RSS of the process.
+    // RssAnon from /proc/pid/status.
+    optional int32 anon_rss_in_kilobytes = 6;
+
+    // The current swap size of the process.
+    // VmSwap from /proc/pid/status.
+    optional int32 swap_in_kilobytes = 7;
+
+    // The sum of rss_in_kilobytes and swap_in_kilobytes.
+    optional int32 anon_rss_and_swap_in_kilobytes = 8;
+}
+
+/*
  * Elapsed real time from SystemClock.
  */
 message SystemElapsedRealtime {
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 475f18a..f69e2d0 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -153,6 +153,9 @@
          {.additiveFields = {3},
           .puller =
                   new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
+        // process_memory_snapshot
+        {android::util::PROCESS_MEMORY_SNAPSHOT,
+         {.puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_SNAPSHOT)}},
         // system_ion_heap_size
         {android::util::SYSTEM_ION_HEAP_SIZE,
          {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ION_HEAP_SIZE)}},
diff --git a/cmds/statsd/src/state/StateListener.h b/cmds/statsd/src/state/StateListener.h
new file mode 100644
index 0000000..a31690a
--- /dev/null
+++ b/cmds/statsd/src/state/StateListener.h
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <utils/RefBase.h>
+
+#include "HashableDimensionKey.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateListener : public virtual RefBase {
+public:
+    StateListener(){};
+
+    virtual ~StateListener(){};
+
+    /**
+     * Interface for handling a state change.
+     *
+     * The old and new state values map to the original state values.
+     * StateTrackers only track the original state values and are unaware
+     * of higher-level state groups. MetricProducers hold information on
+     * state groups and are responsible for mapping original state values to
+     * the correct state group.
+     *
+     * [atomId]: The id of the state atom
+     * [primaryKey]: The primary field values of the state atom
+     * [oldState]: Previous state value before state change
+     * [newState]: Current state value after state change
+     */
+    virtual void onStateChanged(int atomId, const HashableDimensionKey& primaryKey, int oldState,
+                                int newState) = 0;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateManager.cpp b/cmds/statsd/src/state/StateManager.cpp
new file mode 100644
index 0000000..a3059c5
--- /dev/null
+++ b/cmds/statsd/src/state/StateManager.cpp
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+#define DEBUG false  // STOPSHIP if true
+#include "Log.h"
+
+#include "StateManager.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+StateManager& StateManager::getInstance() {
+    static StateManager sStateManager;
+    return sStateManager;
+}
+
+void StateManager::onLogEvent(const LogEvent& event) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mStateTrackers.find(event.GetTagId()) != mStateTrackers.end()) {
+        mStateTrackers[event.GetTagId()]->onLogEvent(event);
+    }
+}
+
+bool StateManager::registerListener(int stateAtomId, wp<StateListener> listener) {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    // Check if state tracker already exists
+    if (mStateTrackers.find(stateAtomId) == mStateTrackers.end()) {
+        // Create a new state tracker iff atom is a state atom
+        auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(stateAtomId);
+        if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) {
+            mStateTrackers[stateAtomId] = new StateTracker(stateAtomId, it->second);
+        } else {
+            ALOGE("StateManager cannot register listener, Atom %d is not a state atom",
+                  stateAtomId);
+            return false;
+        }
+    }
+    mStateTrackers[stateAtomId]->registerListener(listener);
+    return true;
+}
+
+void StateManager::unregisterListener(int stateAtomId, wp<StateListener> listener) {
+    std::unique_lock<std::mutex> lock(mMutex);
+
+    // Hold the sp<> until the lock is released so that ~StateTracker() is
+    // not called while the lock is held.
+    sp<StateTracker> toRemove;
+
+    // Unregister listener from correct StateTracker
+    auto it = mStateTrackers.find(stateAtomId);
+    if (it != mStateTrackers.end()) {
+        it->second->unregisterListener(listener);
+
+        // Remove the StateTracker if it has no listeners
+        if (it->second->getListenersCount() == 0) {
+            toRemove = it->second;
+            mStateTrackers.erase(it);
+        }
+    } else {
+        ALOGE("StateManager cannot unregister listener, StateTracker for atom %d does not exist",
+              stateAtomId);
+    }
+    lock.unlock();
+}
+
+int StateManager::getState(int stateAtomId, const HashableDimensionKey& key) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mStateTrackers.find(stateAtomId) != mStateTrackers.end()) {
+        return mStateTrackers[stateAtomId]->getState(key);
+    }
+
+    return StateTracker::kStateUnknown;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateManager.h b/cmds/statsd/src/state/StateManager.h
new file mode 100644
index 0000000..ce60f14
--- /dev/null
+++ b/cmds/statsd/src/state/StateManager.h
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+#pragma once
+
+//#include <utils/Log.h>
+#include <utils/RefBase.h>
+#include "HashableDimensionKey.h"
+
+#include "state/StateListener.h"
+#include "state/StateTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateManager : public virtual RefBase {
+public:
+    StateManager(){};
+
+    ~StateManager(){};
+
+    // Returns a pointer to the single, shared StateManager object.
+    static StateManager& getInstance();
+
+    // Notifies the correct StateTracker of an event.
+    void onLogEvent(const LogEvent& event);
+
+    // Returns true if stateAtomId is the id of a state atom and notifies the
+    // correct StateTracker to register the listener. If the correct
+    // StateTracker does not exist, a new StateTracker is created.
+    bool registerListener(int stateAtomId, wp<StateListener> listener);
+
+    // Notifies the correct StateTracker to unregister a listener
+    // and removes the tracker if it no longer has any listeners.
+    void unregisterListener(int stateAtomId, wp<StateListener> listener);
+
+    // Queries the correct StateTracker for the state that is mapped to the given
+    // query key.
+    // If the StateTracker doesn't exist, returns StateTracker::kStateUnknown.
+    int getState(int stateAtomId, const HashableDimensionKey& queryKey);
+
+    inline int getStateTrackersCount() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return mStateTrackers.size();
+    }
+
+    inline int getListenersCount(int stateAtomId) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        if (mStateTrackers.find(stateAtomId) != mStateTrackers.end()) {
+            return mStateTrackers[stateAtomId]->getListenersCount();
+        }
+        return -1;
+    }
+
+private:
+  mutable std::mutex mMutex;
+
+  // Maps state atom ids to StateTrackers
+  std::unordered_map<int, sp<StateTracker>> mStateTrackers;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
new file mode 100644
index 0000000..5a91950
--- /dev/null
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+#define DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include "stats_util.h"
+
+#include "StateTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+StateTracker::StateTracker(const int atomId,
+                           const util::StateAtomFieldOptions& stateAtomInfo)
+  : mAtomId(atomId),
+    mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) {
+    // create matcher for each primary field
+    // TODO(tsaichristine): handle when primary field is first uid in chain
+    for (const auto& primary : stateAtomInfo.primaryFields) {
+        Matcher matcher = getSimpleMatcher(atomId, primary);
+        mPrimaryFields.push_back(matcher);
+    }
+
+    // TODO(tsaichristine): set default state, reset state, and nesting
+}
+
+void StateTracker::onLogEvent(const LogEvent& event) {
+    // parse event for primary field values i.e. primary key
+    HashableDimensionKey primaryKey;
+    if (mPrimaryFields.size() > 0) {
+        if (!filterValues(mPrimaryFields, event.getValues(), &primaryKey) ||
+            primaryKey.getValues().size() != mPrimaryFields.size()) {
+            ALOGE("StateTracker error extracting primary key from log event.");
+            handleReset();
+            return;
+        }
+    } else {
+        // atom has no primary fields
+        primaryKey = DEFAULT_DIMENSION_KEY;
+    }
+
+    // parse event for state value
+    Value state;
+    int32_t stateValue;
+    if (!filterValues(mStateField, event.getValues(), &state) || state.getType() != INT) {
+        ALOGE("StateTracker error extracting state from log event. Type: %d", state.getType());
+        handlePartialReset(primaryKey);
+        return;
+    }
+    stateValue = state.int_value;
+
+    if (stateValue == mResetState) {
+        VLOG("StateTracker Reset state: %s", state.toString().c_str());
+        handleReset();
+    }
+
+    // track and update state
+    int32_t oldState = 0;
+    int32_t newState = 0;
+    updateState(primaryKey, stateValue, &oldState, &newState);
+
+    // notify all listeners if state has changed
+    if (oldState != newState) {
+        VLOG("StateTracker updated state");
+        for (auto listener : mListeners) {
+            auto sListener = listener.promote();  // safe access to wp<>
+            if (sListener != nullptr) {
+                sListener->onStateChanged(mAtomId, primaryKey, oldState, newState);
+            }
+        }
+    } else {
+        VLOG("StateTracker NO updated state");
+    }
+}
+
+void StateTracker::registerListener(wp<StateListener> listener) {
+    mListeners.insert(listener);
+}
+
+void StateTracker::unregisterListener(wp<StateListener> listener) {
+    mListeners.erase(listener);
+}
+
+int StateTracker::getState(const HashableDimensionKey& queryKey) const {
+    if (queryKey.getValues().size() == mPrimaryFields.size()) {
+        auto it = mStateMap.find(queryKey);
+        if (it != mStateMap.end()) {
+            return it->second.state;
+        }
+    } else if (queryKey.getValues().size() > mPrimaryFields.size()) {
+        ALOGE("StateTracker query key size > primary key size is illegal");
+    } else {
+        ALOGE("StateTracker query key size < primary key size is not supported");
+    }
+    return mDefaultState;
+}
+
+void StateTracker::handleReset() {
+    VLOG("StateTracker handle reset");
+    for (const auto pair : mStateMap) {
+        for (auto l : mListeners) {
+            auto sl = l.promote();
+            if (sl != nullptr) {
+                sl->onStateChanged(mAtomId, pair.first, pair.second.state, mDefaultState);
+            }
+        }
+    }
+    mStateMap.clear();
+}
+
+void StateTracker::handlePartialReset(const HashableDimensionKey& primaryKey) {
+    VLOG("StateTracker handle partial reset");
+    if (mStateMap.find(primaryKey) != mStateMap.end()) {
+        mStateMap.erase(primaryKey);
+    }
+}
+
+void StateTracker::updateState(const HashableDimensionKey& primaryKey, const int32_t eventState,
+                               int32_t* oldState, int32_t* newState) {
+    // get old state (either current state in map or default state)
+    auto it = mStateMap.find(primaryKey);
+    if (it != mStateMap.end()) {
+        *oldState = it->second.state;
+    } else {
+        *oldState = mDefaultState;
+    }
+
+    // update state map
+    if (eventState == mDefaultState) {
+        // remove (key, state) pair if state returns to default state
+        VLOG("\t StateTracker changed to default state")
+        mStateMap.erase(primaryKey);
+    } else {
+        mStateMap[primaryKey].state = eventState;
+        mStateMap[primaryKey].count = 1;
+    }
+    *newState = eventState;
+
+    // TODO(tsaichristine): support atoms with nested counting
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
new file mode 100644
index 0000000..f22706c
--- /dev/null
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <statslog.h>
+#include <utils/RefBase.h>
+#include "HashableDimensionKey.h"
+#include "logd/LogEvent.h"
+
+#include "state/StateListener.h"
+
+#include <unordered_map>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateTracker : public virtual RefBase {
+public:
+    StateTracker(const int atomId, const util::StateAtomFieldOptions& stateAtomInfo);
+
+    virtual ~StateTracker(){};
+
+    // Updates state map and notifies all listeners if a state change occurs.
+    // Checks if a state change has occurred by getting the state value from
+    // the log event and comparing the old and new states.
+    void onLogEvent(const LogEvent& event);
+
+    // Adds new listeners to set of StateListeners. If a listener is already
+    // registered, it is ignored.
+    void registerListener(wp<StateListener> listener);
+
+    void unregisterListener(wp<StateListener> listener);
+
+    // Returns the state value mapped to the given query key.
+    // If the key isn't mapped to a state or the key size doesn't match the
+    // primary key size, the default state is returned.
+    int getState(const HashableDimensionKey& queryKey) const;
+
+    inline int getListenersCount() const {
+        return mListeners.size();
+    }
+
+    const static int kStateUnknown = -1;
+
+private:
+    struct StateValueInfo {
+        int32_t state;  // state value
+        int count;      // nested count (only used for binary states)
+    };
+
+    const int32_t mAtomId;  // id of the state atom being tracked
+
+    Matcher mStateField;  // matches the atom's exclusive state field
+
+    std::vector<Matcher> mPrimaryFields;  // matches the atom's primary fields
+
+    int32_t mDefaultState = kStateUnknown;
+
+    int32_t mResetState;
+
+    // Maps primary key to state value info
+    std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap;
+
+    // Set of all StateListeners (objects listening for state changes)
+    std::set<wp<StateListener>> mListeners;
+
+    // Reset all state values in map to default state
+    void handleReset();
+
+    // Reset only the state value mapped to primary key to default state
+    void handlePartialReset(const HashableDimensionKey& primaryKey);
+
+    // Update the StateMap based on the received state value.
+    // Store the old and new states.
+    void updateState(const HashableDimensionKey& primaryKey, const int32_t eventState,
+                     int32_t* oldState, int32_t* newState);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
new file mode 100644
index 0000000..c89ffea
--- /dev/null
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -0,0 +1,356 @@
+/*
+ * 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.
+ */
+#include <gtest/gtest.h>
+#include "state/StateManager.h"
+#include "state/StateTracker.h"
+#include "state/StateListener.h"
+
+#include "tests/statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Mock StateListener class for testing.
+ * Stores primary key and state pairs.
+ */
+class TestStateListener : public virtual StateListener {
+public:
+    TestStateListener(){};
+
+    virtual ~TestStateListener(){};
+
+    struct Update {
+        Update(const HashableDimensionKey& key, int state) : mKey(key), mState(state){};
+        HashableDimensionKey mKey;
+        int mState;
+    };
+
+    std::vector<Update> updates;
+
+    void onStateChanged(int stateAtomId, const HashableDimensionKey& primaryKey, int oldState,
+                        int newState) {
+        updates.emplace_back(primaryKey, newState);
+    }
+};
+
+// START: build event functions.
+// State with no primary fields - ScreenStateChanged
+std::shared_ptr<LogEvent> buildScreenEvent(int state) {
+    std::shared_ptr<LogEvent> event =
+            std::make_shared<LogEvent>(android::util::SCREEN_STATE_CHANGED, 1000 /*timestamp*/);
+    event->write((int32_t)state);
+    event->init();
+    return event;
+}
+
+// State with one primary field - UidProcessStateChanged
+std::shared_ptr<LogEvent> buildUidProcessEvent(int uid, int state) {
+    std::shared_ptr<LogEvent> event =
+            std::make_shared<LogEvent>(android::util::UID_PROCESS_STATE_CHANGED, 1000 /*timestamp*/);
+    event->write((int32_t)uid);
+    event->write((int32_t)state);
+    event->init();
+    return event;
+}
+
+// State with multiple primary fields - OverlayStateChanged
+std::shared_ptr<LogEvent> buildOverlayEvent(int uid, const std::string& packageName, int state) {
+    std::shared_ptr<LogEvent> event =
+            std::make_shared<LogEvent>(android::util::OVERLAY_STATE_CHANGED, 1000 /*timestamp*/);
+    event->write((int32_t)uid);
+    event->write(packageName);
+    event->write(true);  // using_alert_window
+    event->write((int32_t)state);
+    event->init();
+    return event;
+}
+
+std::shared_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName, int state) {
+    std::shared_ptr<LogEvent> event =
+            std::make_shared<LogEvent>(android::util::OVERLAY_STATE_CHANGED, 1000 /*timestamp*/);
+    event->write((int32_t)uid);
+    event->write(packageName);
+    event->write((int32_t)state);
+    event->init();
+    return event;
+}
+// END: build event functions.
+
+// START: get primary key functions
+void getUidProcessKey(int uid, HashableDimensionKey* key) {
+    int pos1[] = {1, 0, 0};
+    Field field1(27 /* atom id */, pos1, 0 /* depth */);
+    Value value1((int32_t)uid);
+
+    key->addValue(FieldValue(field1, value1));
+}
+
+void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) {
+    int pos1[] = {1, 0, 0};
+    int pos2[] = {2, 0, 0};
+
+    Field field1(59 /* atom id */, pos1, 0 /* depth */);
+    Field field2(59 /* atom id */, pos2, 0 /* depth */);
+
+    Value value1((int32_t)uid);
+    Value value2(packageName);
+
+    key->addValue(FieldValue(field1, value1));
+    key->addValue(FieldValue(field2, value2));
+}
+// END: get primary key functions
+
+TEST(StateListenerTest, TestStateListenerWeakPointer) {
+    sp<TestStateListener> listener = new TestStateListener();
+    wp<TestStateListener> wListener = listener;
+    listener = nullptr;  // let go of listener
+    EXPECT_TRUE(wListener.promote() == nullptr);
+}
+
+TEST(StateManagerTest, TestStateManagerGetInstance) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager& mgr = StateManager::getInstance();
+
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+}
+
+/**
+ * Test registering listeners to StateTrackers
+ *
+ * - StateManager will create a new StateTracker if it doesn't already exist
+ * and then register the listener to the StateTracker.
+ * - If a listener is already registered to a StateTracker, it is not added again.
+ * - StateTrackers are only created for atoms that are state atoms.
+ */
+TEST(StateTrackerTest, TestRegisterListener) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    sp<TestStateListener> listener2 = new TestStateListener();
+    StateManager mgr;
+
+    // Register listener to non-existing StateTracker
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Register listener to existing StateTracker
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(2, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Register already registered listener to existing StateTracker
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(2, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Register listener to non-state atom
+    mgr.registerListener(android::util::BATTERY_LEVEL_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+}
+
+/**
+ * Test unregistering listeners from StateTrackers
+ *
+ * - StateManager will unregister listeners from a StateTracker only if the
+ * StateTracker exists and the listener is registered to the StateTracker.
+ * - Once all listeners are removed from a StateTracker, the StateTracker
+ * is also removed.
+ */
+TEST(StateTrackerTest, TestUnregisterListener) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    sp<TestStateListener> listener2 = new TestStateListener();
+    StateManager mgr;
+
+    // Unregister listener from non-existing StateTracker
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    mgr.unregisterListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    EXPECT_EQ(-1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Unregister non-registered listener from existing StateTracker
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+    mgr.unregisterListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Unregister second-to-last listener from StateTracker
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    mgr.unregisterListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Unregister last listener from StateTracker
+    mgr.unregisterListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    EXPECT_EQ(-1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+}
+
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged correctly
+ * updates listener for states without primary keys.
+ */
+TEST(StateTrackerTest, TestStateChangeNoPrimaryFields) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+
+    // log event
+    std::shared_ptr<LogEvent> event =
+            buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    mgr.onLogEvent(*event);
+
+    // check listener was updated
+    EXPECT_EQ(1, listener1->updates.size());
+    EXPECT_EQ(DEFAULT_DIMENSION_KEY, listener1->updates[0].mKey);
+    EXPECT_EQ(2, listener1->updates[0].mState);
+
+    // check StateTracker was updated by querying for state
+    HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY;
+    EXPECT_EQ(2, mgr.getState(android::util::SCREEN_STATE_CHANGED, queryKey));
+}
+
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged correctly
+ * updates listener for states with primary keys.
+ */
+TEST(StateTrackerTest, TestStateChangeOnePrimaryField) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener1);
+
+    // log event
+    std::shared_ptr<LogEvent> event = buildUidProcessEvent(
+            1000,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  //  state value: 1002
+    mgr.onLogEvent(*event);
+
+    // check listener was updated
+    EXPECT_EQ(1, listener1->updates.size());
+    EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
+    EXPECT_EQ(1002, listener1->updates[0].mState);
+
+    // check StateTracker was updated by querying for state
+    HashableDimensionKey queryKey;
+    getUidProcessKey(1000, &queryKey);
+    EXPECT_EQ(1002, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey));
+}
+
+TEST(StateTrackerTest, TestStateChangeMultiplePrimaryFields) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener1);
+
+    // log event
+    std::shared_ptr<LogEvent> event = buildOverlayEvent(1000, "package1", 1);  // state: ENTERED
+    mgr.onLogEvent(*event);
+
+    // check listener update
+    EXPECT_EQ(1, listener1->updates.size());
+    EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
+    EXPECT_EQ(1, listener1->updates[0].mState);
+}
+
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged
+ * when there is an error extracting state from log event. Listener is not
+ * updated of state change.
+ */
+TEST(StateTrackerTest, TestStateChangeEventError) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener1);
+
+    // log event
+    std::shared_ptr<LogEvent> event =
+            buildIncorrectOverlayEvent(1000, "package1", 1);  // state: ENTERED
+    mgr.onLogEvent(*event);
+
+    // check listener update
+    EXPECT_EQ(0, listener1->updates.size());
+}
+
+TEST(StateTrackerTest, TestStateQuery) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    sp<TestStateListener> listener2 = new TestStateListener();
+    sp<TestStateListener> listener3 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener2);
+    mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener3);
+
+    std::shared_ptr<LogEvent> event1 = buildUidProcessEvent(
+            1000,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  // state value: 1002
+    std::shared_ptr<LogEvent> event2 = buildUidProcessEvent(
+            1001,
+            android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE);  // state value: 1003
+    std::shared_ptr<LogEvent> event3 = buildUidProcessEvent(
+            1002,
+            android::app::ProcessStateEnum::PROCESS_STATE_PERSISTENT);  // state value: 1000
+    std::shared_ptr<LogEvent> event4 = buildUidProcessEvent(
+            1001,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  // state value: 1002
+    std::shared_ptr<LogEvent> event5 =
+            buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON);  // state value:
+    std::shared_ptr<LogEvent> event6 = buildOverlayEvent(1000, "package1", 1);
+    std::shared_ptr<LogEvent> event7 = buildOverlayEvent(1000, "package2", 2);
+
+    mgr.onLogEvent(*event1);
+    mgr.onLogEvent(*event2);
+    mgr.onLogEvent(*event3);
+    mgr.onLogEvent(*event5);
+    mgr.onLogEvent(*event5);
+    mgr.onLogEvent(*event6);
+    mgr.onLogEvent(*event7);
+
+    // Query for UidProcessState of uid 1001
+    HashableDimensionKey queryKey1;
+    getUidProcessKey(1001, &queryKey1);
+    EXPECT_EQ(1003, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+
+    // Query for UidProcessState of uid 1004 - not in state map
+    HashableDimensionKey queryKey2;
+    getUidProcessKey(1004, &queryKey2);
+    EXPECT_EQ(-1,
+              mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey2));  // default state
+
+    // Query for UidProcessState of uid 1001 - after change in state
+    mgr.onLogEvent(*event4);
+    EXPECT_EQ(1002, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+
+    // Query for ScreenState
+    EXPECT_EQ(2, mgr.getState(android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
+
+    // Query for OverlayState of uid 1000, package name "package2"
+    HashableDimensionKey queryKey3;
+    getOverlayKey(1000, "package2", &queryKey3);
+    EXPECT_EQ(2, mgr.getState(android::util::OVERLAY_STATE_CHANGED, queryKey3));
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 563174b..1649e8b 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -853,12 +853,14 @@
     public static final int OP_ACCESS_ACCESSIBILITY = 88;
     /** @hide Read the device identifiers (IMEI / MEID, IMSI, SIM / Build serial) */
     public static final int OP_READ_DEVICE_IDENTIFIERS = 89;
+    /** @hide Read location metadata from media */
+    public static final int OP_ACCESS_MEDIA_LOCATION = 90;
     /** @hide Query all apps on device, regardless of declarations in the calling app manifest */
-    public static final int OP_QUERY_ALL_PACKAGES = 90;
+    public static final int OP_QUERY_ALL_PACKAGES = 91;
 
     /** @hide */
     @UnsupportedAppUsage
-    public static final int _NUM_OP = 91;
+    public static final int _NUM_OP = 92;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1134,6 +1136,8 @@
     /** @hide Has a legacy (non-isolated) view of storage. */
     @SystemApi @TestApi
     public static final String OPSTR_LEGACY_STORAGE = "android:legacy_storage";
+    /** @hide Read location metadata from media */
+    public static final String OPSTR_ACCESS_MEDIA_LOCATION = "android:access_media_location";
 
     /** @hide Interact with accessibility. */
     @SystemApi
@@ -1180,6 +1184,7 @@
             // Storage
             OP_READ_EXTERNAL_STORAGE,
             OP_WRITE_EXTERNAL_STORAGE,
+            OP_ACCESS_MEDIA_LOCATION,
             // Location
             OP_COARSE_LOCATION,
             OP_FINE_LOCATION,
@@ -1322,6 +1327,7 @@
             OP_LEGACY_STORAGE,                  // LEGACY_STORAGE
             OP_ACCESS_ACCESSIBILITY,            // ACCESS_ACCESSIBILITY
             OP_READ_DEVICE_IDENTIFIERS,         // READ_DEVICE_IDENTIFIERS
+            OP_ACCESS_MEDIA_LOCATION,           // ACCESS_MEDIA_LOCATION
             OP_QUERY_ALL_PACKAGES,              // QUERY_ALL_PACKAGES
     };
 
@@ -1419,6 +1425,7 @@
             OPSTR_LEGACY_STORAGE,
             OPSTR_ACCESS_ACCESSIBILITY,
             OPSTR_READ_DEVICE_IDENTIFIERS,
+            OPSTR_ACCESS_MEDIA_LOCATION,
             OPSTR_QUERY_ALL_PACKAGES,
     };
 
@@ -1517,6 +1524,7 @@
             "LEGACY_STORAGE",
             "ACCESS_ACCESSIBILITY",
             "READ_DEVICE_IDENTIFIERS",
+            "ACCESS_MEDIA_LOCATION",
             "QUERY_ALL_PACKAGES",
     };
 
@@ -1616,6 +1624,7 @@
             null, // no permission for OP_LEGACY_STORAGE
             null, // no permission for OP_ACCESS_ACCESSIBILITY
             null, // no direct permission for OP_READ_DEVICE_IDENTIFIERS
+            Manifest.permission.ACCESS_MEDIA_LOCATION,
             null, // no permission for OP_QUERY_ALL_PACKAGES
     };
 
@@ -1715,6 +1724,7 @@
             null, // LEGACY_STORAGE
             null, // ACCESS_ACCESSIBILITY
             null, // READ_DEVICE_IDENTIFIERS
+            null, // ACCESS_MEDIA_LOCATION
             null, // QUERY_ALL_PACKAGES
     };
 
@@ -1813,6 +1823,7 @@
             false, // LEGACY_STORAGE
             false, // ACCESS_ACCESSIBILITY
             false, // READ_DEVICE_IDENTIFIERS
+            false, // ACCESS_MEDIA_LOCATION
             false, // QUERY_ALL_PACKAGES
     };
 
@@ -1910,6 +1921,7 @@
             AppOpsManager.MODE_DEFAULT, // LEGACY_STORAGE
             AppOpsManager.MODE_ALLOWED, // ACCESS_ACCESSIBILITY
             AppOpsManager.MODE_ERRORED, // READ_DEVICE_IDENTIFIERS
+            AppOpsManager.MODE_ALLOWED, // ALLOW_MEDIA_LOCATION
             AppOpsManager.MODE_DEFAULT, // QUERY_ALL_PACKAGES
     };
 
@@ -2011,6 +2023,7 @@
             false, // LEGACY_STORAGE
             false, // ACCESS_ACCESSIBILITY
             false, // READ_DEVICE_IDENTIFIERS
+            false, // ACCESS_MEDIA_LOCATION
             false, // QUERY_ALL_PACKAGES
     };
 
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
index 64f886a..df6533a 100644
--- a/core/java/android/app/AsyncNotedAppOp.java
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -238,7 +238,7 @@
             time = 1566503083973L,
             codegenVersion = "1.0.0",
             sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
-            inputSignatures = "private final @android.annotation.IntRange(from=0L, to=90L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mNotingPackageName\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
+            inputSignatures = "private final @android.annotation.IntRange(from=0L, to=91L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mNotingPackageName\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index def1f45..35c7104 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -26,6 +26,15 @@
                     "include-filter": "com.android.server.appop"
                 }
             ]
+        },
+        {
+            "file_patterns": ["(/|^)AppOpsManager.java"],
+            "name": "CtsPermission2TestCases",
+            "options": [
+                {
+                    "include-filter": "android.permission2.cts.RuntimePermissionProperties"
+                }
+            ]
         }
     ],
     "postsubmit": [
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 02cac23..64ddfc1 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1551,7 +1551,8 @@
      * scopes will be sent in an {@code ArrayList<String>} extra identified by the
      * {@link #EXTRA_DELEGATION_SCOPES} key.
      *
-     * <p class=”note”> Note: This is a protected intent that can only be sent by the system.</p>
+     * <p class="note"><b>Note:</b> This is a protected intent that can only be sent by the
+     * system.</p>
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED =
@@ -2609,6 +2610,7 @@
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param quality The new desired quality. One of {@link #PASSWORD_QUALITY_UNSPECIFIED},
+     *            {@link #PASSWORD_QUALITY_BIOMETRIC_WEAK},
      *            {@link #PASSWORD_QUALITY_SOMETHING}, {@link #PASSWORD_QUALITY_NUMERIC},
      *            {@link #PASSWORD_QUALITY_NUMERIC_COMPLEX}, {@link #PASSWORD_QUALITY_ALPHABETIC},
      *            {@link #PASSWORD_QUALITY_ALPHANUMERIC} or {@link #PASSWORD_QUALITY_COMPLEX}.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8dfe00a..6d88fea 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3078,8 +3078,11 @@
      * because the app was updated to support runtime permissions, the
      * the permission will be revoked in the upgrade process.
      *
+     * @deprecated Renamed to {@link #FLAG_PERMISSION_REVOKED_COMPAT}.
+     *
      * @hide
      */
+    @Deprecated
     @SystemApi
     @TestApi
     public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE =  1 << 3;
@@ -3202,6 +3205,18 @@
     public static final int FLAG_PERMISSION_GRANTED_BY_ROLE =  1 << 15;
 
     /**
+     * Permission flag: The permission should have been revoked but is kept granted for
+     * compatibility. The data protected by the permission should be protected by a no-op (empty
+     * list, default error, etc) instead of crashing the client. The permission will be revoked if
+     * the app is upgraded to supports it.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int FLAG_PERMISSION_REVOKED_COMPAT =  FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+
+    /**
      * Permission flags: Bitwise or of all permission flags allowing an
      * exemption for a restricted permission.
      * @hide
@@ -3241,7 +3256,8 @@
             | FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
             | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
             | FLAG_PERMISSION_APPLY_RESTRICTION
-            | FLAG_PERMISSION_GRANTED_BY_ROLE;
+            | FLAG_PERMISSION_GRANTED_BY_ROLE
+            | FLAG_PERMISSION_REVOKED_COMPAT;
 
     /**
      * Injected activity in app that forwards user to setting activity of that app.
@@ -4017,7 +4033,8 @@
             FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT,
             FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT,
             FLAG_PERMISSION_APPLY_RESTRICTION,
-            FLAG_PERMISSION_GRANTED_BY_ROLE
+            FLAG_PERMISSION_GRANTED_BY_ROLE,
+            FLAG_PERMISSION_REVOKED_COMPAT
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PermissionFlags {}
@@ -7086,7 +7103,6 @@
             case FLAG_PERMISSION_POLICY_FIXED: return "POLICY_FIXED";
             case FLAG_PERMISSION_SYSTEM_FIXED: return "SYSTEM_FIXED";
             case FLAG_PERMISSION_USER_SET: return "USER_SET";
-            case FLAG_PERMISSION_REVOKE_ON_UPGRADE: return "REVOKE_ON_UPGRADE";
             case FLAG_PERMISSION_USER_FIXED: return "USER_FIXED";
             case FLAG_PERMISSION_REVIEW_REQUIRED: return "REVIEW_REQUIRED";
             case FLAG_PERMISSION_REVOKE_WHEN_REQUESTED: return "REVOKE_WHEN_REQUESTED";
@@ -7097,6 +7113,7 @@
             case FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT: return "RESTRICTION_UPGRADE_EXEMPT";
             case FLAG_PERMISSION_APPLY_RESTRICTION: return "APPLY_RESTRICTION";
             case FLAG_PERMISSION_GRANTED_BY_ROLE: return "GRANTED_BY_ROLE";
+            case FLAG_PERMISSION_REVOKED_COMPAT: return "REVOKED_COMPAT";
             default: return Integer.toString(flag);
         }
     }
diff --git a/core/java/com/android/internal/policy/KeyInterceptionInfo.java b/core/java/com/android/internal/policy/KeyInterceptionInfo.java
new file mode 100644
index 0000000..964be01
--- /dev/null
+++ b/core/java/com/android/internal/policy/KeyInterceptionInfo.java
@@ -0,0 +1,35 @@
+/*
+ * 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.policy;
+
+
+/**
+ * Stores a snapshot of window information used to decide whether to intercept a key event.
+ */
+public class KeyInterceptionInfo {
+    // Window layout params attributes.
+    public final int layoutParamsType;
+    public final int layoutParamsPrivateFlags;
+    // Debug friendly name to help identify the window
+    public final String windowTitle;
+
+    public KeyInterceptionInfo(int type, int flags, String title) {
+        layoutParamsType = type;
+        layoutParamsPrivateFlags = flags;
+        windowTitle = title;
+    }
+}
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 79167ab..15b98af 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -48,6 +48,13 @@
 
     repeated int32 started_users = 2;
 
+    message JobRestriction {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional .android.app.job.StopReasonEnum reason = 1;
+        optional bool is_restricting = 2;
+    }
+
     message RegisteredJob {
         option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
@@ -56,20 +63,22 @@
 
         optional bool is_job_ready_to_be_executed = 10;
         // A job is ready to be executed if:
-        // is_job_ready && are_users_started && !is_job_thermal_constrained && !is_job_pending &&
+        // is_job_ready && are_users_started && !is_job_restricted && !is_job_pending &&
         // !is_job_currently_active && !is_uid_backing_up &&
         // is_component_usable.
         optional bool is_job_ready = 3;
         optional bool are_users_started = 4;
-        optional bool is_job_thermal_constrained = 11;
+        optional bool is_job_restricted = 11;
         optional bool is_job_pending = 5;
         optional bool is_job_currently_active = 6;
         optional bool is_uid_backing_up = 7;
         optional bool is_component_usable = 8;
 
+        repeated JobRestriction restrictions = 12;
+
         reserved 9; // last_run_heartbeat
 
-        // Next tag: 12
+        // Next tag: 13
     }
     repeated RegisteredJob registered_jobs = 3;
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e23c75e..1d590ea 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -643,6 +643,7 @@
     <!-- Grouping for platform runtime permissions is not accessible to apps
          @hide
          @SystemApi
+         @TestApi
     -->
     <permission-group android:name="android.permission-group.UNDEFINED"
         android:priority="100" />
diff --git a/core/res/TEST_MAPPING b/core/res/TEST_MAPPING
index ccd91a4..9185bae 100644
--- a/core/res/TEST_MAPPING
+++ b/core/res/TEST_MAPPING
@@ -5,6 +5,9 @@
             "options": [
                 {
                     "include-filter": "android.permission2.cts.PermissionPolicyTest#platformPermissionPolicyIsUnaltered"
+                },
+                {
+                    "include-filter": "android.permission2.cts.RuntimePermissionProperties"
                 }
             ]
         }
diff --git a/data/etc/TEST_MAPPING b/data/etc/TEST_MAPPING
new file mode 100644
index 0000000..1a5db2f
--- /dev/null
+++ b/data/etc/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+    "presubmit": [
+        {
+            "file_patterns": ["(/|^)platform.xml"],
+            "name": "CtsPermission2TestCases",
+            "options": [
+                {
+                    "include-filter": "android.permission2.cts.RuntimePermissionProperties"
+                }
+            ]
+        }
+    ]
+}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index d66930a..2ab7845a 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -206,6 +206,10 @@
                       targetSdk="29">
         <new-permission name="android.permission.ACCESS_BACKGROUND_LOCATION" />
     </split-permission>
+    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
+                      targetSdk="29">
+        <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
+    </split-permission>
 
     <!-- This is a list of all the libraries available for application
          code to link against. -->
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index b9945cc..96ac0f9 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -637,7 +637,7 @@
      * @see #setOrientation(Orientation)
      */
     public Orientation getOrientation() {
-        return mGradientState.getOrientation();
+        return mGradientState.mOrientation;
     }
 
     /**
@@ -653,7 +653,7 @@
      * @see #getOrientation()
      */
     public void setOrientation(Orientation orientation) {
-        mGradientState.setOrientation(orientation);
+        mGradientState.mOrientation = orientation;
         mGradientIsDirty = true;
         invalidateSelf();
     }
@@ -1270,7 +1270,7 @@
 
                 if (st.mGradient == LINEAR_GRADIENT) {
                     final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f;
-                    switch (st.getOrientation()) {
+                    switch (st.mOrientation) {
                     case TOP_BOTTOM:
                         x0 = r.left;            y0 = r.top;
                         x1 = x0;                y1 = level * r.bottom;
@@ -1759,6 +1759,33 @@
         int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);
         st.mAngle = ((angle % 360) + 360) % 360; // offset negative angle measures
 
+        switch (st.mAngle) {
+            case 0:
+                st.mOrientation = Orientation.LEFT_RIGHT;
+                break;
+            case 45:
+                st.mOrientation = Orientation.BL_TR;
+                break;
+            case 90:
+                st.mOrientation = Orientation.BOTTOM_TOP;
+                break;
+            case 135:
+                st.mOrientation = Orientation.BR_TL;
+                break;
+            case 180:
+                st.mOrientation = Orientation.RIGHT_LEFT;
+                break;
+            case 225:
+                st.mOrientation = Orientation.TR_BL;
+                break;
+            case 270:
+                st.mOrientation = Orientation.TOP_BOTTOM;
+                break;
+            case 315:
+                st.mOrientation = Orientation.TL_BR;
+                break;
+        }
+
         final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius);
         if (tv != null) {
             final float radius;
@@ -1981,7 +2008,7 @@
         int[] mAttrPadding;
 
         public GradientState(Orientation orientation, int[] gradientColors) {
-            setOrientation(orientation);
+            mOrientation = orientation;
             setGradientColors(gradientColors);
         }
 
@@ -2184,93 +2211,11 @@
             mCenterY = y;
         }
 
-        public void setOrientation(Orientation orientation) {
-            // Update the angle here so that subsequent attempts to obtain the orientation
-            // from the angle overwrite previously configured values during inflation
-            mAngle = getAngleFromOrientation(orientation);
-            mOrientation = orientation;
-        }
-
         @NonNull
         public Orientation getOrientation() {
-            updateGradientStateOrientation();
             return mOrientation;
         }
 
-        /**
-         * Update the orientation of the gradient based on the given angle only if the type is
-         * {@link #LINEAR_GRADIENT}
-         */
-        private void updateGradientStateOrientation() {
-            if (mGradient == LINEAR_GRADIENT) {
-                int angle = mAngle;
-                if (angle % 45 != 0) {
-                    throw new IllegalArgumentException("Linear gradient requires 'angle' attribute "
-                            + "to be a multiple of 45");
-                }
-
-                Orientation orientation;
-                switch (angle) {
-                    case 0:
-                        orientation = Orientation.LEFT_RIGHT;
-                        break;
-                    case 45:
-                        orientation = Orientation.BL_TR;
-                        break;
-                    case 90:
-                        orientation = Orientation.BOTTOM_TOP;
-                        break;
-                    case 135:
-                        orientation = Orientation.BR_TL;
-                        break;
-                    case 180:
-                        orientation = Orientation.RIGHT_LEFT;
-                        break;
-                    case 225:
-                        orientation = Orientation.TR_BL;
-                        break;
-                    case 270:
-                        orientation = Orientation.TOP_BOTTOM;
-                        break;
-                    case 315:
-                        orientation = Orientation.TL_BR;
-                        break;
-                    default:
-                        // Should not get here as exception is thrown above if angle is not multiple
-                        // of 45 degrees
-                        orientation = Orientation.LEFT_RIGHT;
-                        break;
-                }
-                mOrientation = orientation;
-            }
-        }
-
-        private int getAngleFromOrientation(@Nullable Orientation orientation) {
-            if (orientation != null) {
-                switch (orientation) {
-                    default:
-                    case LEFT_RIGHT:
-                        return 0;
-                    case BL_TR:
-                        return 45;
-                    case BOTTOM_TOP:
-                        return 90;
-                    case BR_TL:
-                        return 135;
-                    case RIGHT_LEFT:
-                        return 180;
-                    case TR_BL:
-                        return 225;
-                    case TOP_BOTTOM:
-                        return 270;
-                    case TL_BR:
-                        return 315;
-                }
-            } else {
-                return 0;
-            }
-        }
-
         public void setGradientColors(@Nullable int[] colors) {
             mGradientColors = colors;
             mSolidColors = null;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 67c181b..3010206 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -569,6 +569,7 @@
     // Set up the overdraw canvas.
     SkImageInfo offscreenInfo = SkImageInfo::MakeA8(surface->width(), surface->height());
     sk_sp<SkSurface> offscreen = surface->makeSurface(offscreenInfo);
+    LOG_ALWAYS_FATAL_IF(!offscreen, "Failed to create offscreen SkSurface for overdraw viz.");
     SkOverdrawCanvas overdrawCanvas(offscreen->getCanvas());
 
     // Fake a redraw to replay the draw commands.  This will increment the alpha channel
diff --git a/location/lib/Android.bp b/location/lib/Android.bp
index b36aa03..fe0f669 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -16,12 +16,11 @@
 
 java_sdk_library {
     name: "com.android.location.provider",
-    srcs: [
-        "java/**/*.java",
-        ":framework-all-sources",
-    ],
+    srcs: ["java/**/*.java"],
+    api_srcs: [":framework-all-sources"],
     libs: [
         "androidx.annotation_annotation",
+        "framework-all",
     ],
     api_packages: ["com.android.location.provider"],
 }
diff --git a/media/lib/signer/Android.bp b/media/lib/signer/Android.bp
index 2286c53..6b03e4d 100644
--- a/media/lib/signer/Android.bp
+++ b/media/lib/signer/Android.bp
@@ -16,9 +16,8 @@
 
 java_sdk_library {
     name: "com.android.mediadrm.signer",
-    srcs: [
-        "java/**/*.java",
-        ":framework-all-sources",
-    ],
+    srcs: ["java/**/*.java"],
+    api_srcs: [":framework-all-sources"],
+    libs: ["framework-all"],
     api_packages: ["com.android.mediadrm.signer"],
 }
diff --git a/packages/BackupEncryption/proto/key_value_listing.proto b/packages/BackupEncryption/proto/key_value_listing.proto
new file mode 100644
index 0000000..001e697
--- /dev/null
+++ b/packages/BackupEncryption/proto/key_value_listing.proto
@@ -0,0 +1,40 @@
+/*
+ * 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
+ */
+
+syntax = "proto2";
+
+package android_backup_crypto;
+
+option java_package = "com.android.server.backup.encryption.protos";
+option java_outer_classname = "KeyValueListingProto";
+
+// An entry of a key-value pair.
+message KeyValueEntry {
+  // Plaintext key of the key-value pair.
+  optional string key = 1;
+  // SHA-256 MAC of the plaintext of the chunk containing the pair
+  optional bytes hash = 2;
+}
+
+// Describes the key/value pairs currently in the backup blob, mapping from the
+// plaintext key to the hash of the chunk containing the pair.
+//
+// This is local state stored on the device. It is never sent to the
+// backup server. See ChunkOrdering for how the device restores the
+// key-value pairs in the correct order.
+message KeyValueListing {
+  repeated KeyValueEntry entries = 1;
+}
diff --git a/packages/BackupEncryption/proto/key_value_pair.proto b/packages/BackupEncryption/proto/key_value_pair.proto
new file mode 100644
index 0000000..177fa30
--- /dev/null
+++ b/packages/BackupEncryption/proto/key_value_pair.proto
@@ -0,0 +1,31 @@
+/*
+ * 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
+ */
+
+syntax = "proto2";
+
+package android_backup_crypto;
+
+option java_package = "com.android.server.backup.encryption.protos";
+option java_outer_classname = "KeyValuePairProto";
+
+// Serialized form of a key-value pair, when it is to be encrypted in a blob.
+// The backup blob for a key-value database consists of repeated encrypted
+// key-value pairs like this, in a randomized order. See ChunkOrdering for how
+// these are then reconstructed during a restore.
+message KeyValuePair {
+  optional string key = 1;
+  optional bytes value = 2;
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutput.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutput.java
new file mode 100644
index 0000000..56e1c05
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutput.java
@@ -0,0 +1,111 @@
+/*
+ * 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.backup.encryption.kv;
+
+import static com.android.internal.util.Preconditions.checkState;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.ChunkHasher;
+import com.android.server.backup.encryption.protos.nano.KeyValuePairProto;
+import com.android.server.backup.encryption.tasks.DecryptedChunkOutput;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Builds a key value backup set from plaintext chunks. Computes a digest over the sorted SHA-256
+ * hashes of the chunks.
+ */
+public class DecryptedChunkKvOutput implements DecryptedChunkOutput {
+    @VisibleForTesting static final String DIGEST_ALGORITHM = "SHA-256";
+
+    private final ChunkHasher mChunkHasher;
+    private final List<KeyValuePairProto.KeyValuePair> mUnsortedPairs = new ArrayList<>();
+    private final List<ChunkHash> mUnsortedHashes = new ArrayList<>();
+    private boolean mClosed;
+
+    /** Constructs a new instance which computers the digest using the given hasher. */
+    public DecryptedChunkKvOutput(ChunkHasher chunkHasher) {
+        mChunkHasher = chunkHasher;
+    }
+
+    @Override
+    public DecryptedChunkOutput open() {
+        // As we don't have any resources there is nothing to open.
+        return this;
+    }
+
+    @Override
+    public void processChunk(byte[] plaintextBuffer, int length)
+            throws IOException, InvalidKeyException {
+        checkState(!mClosed, "Cannot process chunk after close()");
+        KeyValuePairProto.KeyValuePair kvPair = new KeyValuePairProto.KeyValuePair();
+        KeyValuePairProto.KeyValuePair.mergeFrom(kvPair, plaintextBuffer, 0, length);
+        mUnsortedPairs.add(kvPair);
+        // TODO(b/71492289): Update ChunkHasher to accept offset and length so we don't have to copy
+        // the buffer into a smaller array.
+        mUnsortedHashes.add(mChunkHasher.computeHash(Arrays.copyOf(plaintextBuffer, length)));
+    }
+
+    @Override
+    public void close() {
+        // As we don't have any resources there is nothing to close.
+        mClosed = true;
+    }
+
+    @Override
+    public byte[] getDigest() throws NoSuchAlgorithmException {
+        checkState(mClosed, "Must close() before getDigest()");
+        MessageDigest digest = getMessageDigest();
+        Collections.sort(mUnsortedHashes);
+        for (ChunkHash hash : mUnsortedHashes) {
+            digest.update(hash.getHash());
+        }
+        return digest.digest();
+    }
+
+    private static MessageDigest getMessageDigest() throws NoSuchAlgorithmException {
+        return MessageDigest.getInstance(DIGEST_ALGORITHM);
+    }
+
+    /**
+     * Returns the key value pairs from the backup, sorted lexicographically by key.
+     *
+     * <p>You must call {@link #close} first.
+     */
+    public List<KeyValuePairProto.KeyValuePair> getPairs() {
+        checkState(mClosed, "Must close() before getPairs()");
+        Collections.sort(
+                mUnsortedPairs,
+                new Comparator<KeyValuePairProto.KeyValuePair>() {
+                    @Override
+                    public int compare(
+                            KeyValuePairProto.KeyValuePair o1, KeyValuePairProto.KeyValuePair o2) {
+                        return o1.key.compareTo(o2.key);
+                    }
+                });
+        return mUnsortedPairs;
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/KeyValueListingBuilder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/KeyValueListingBuilder.java
new file mode 100644
index 0000000..b3518e1
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/KeyValueListingBuilder.java
@@ -0,0 +1,77 @@
+/*
+ * 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.backup.encryption.kv;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.protos.nano.KeyValueListingProto;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Builds a {@link KeyValueListingProto.KeyValueListing}, which is a nano proto and so has no
+ * builder.
+ */
+public class KeyValueListingBuilder {
+    private final List<KeyValueListingProto.KeyValueEntry> mEntries = new ArrayList<>();
+
+    /** Adds a new pair entry to the listing. */
+    public KeyValueListingBuilder addPair(String key, ChunkHash hash) {
+        checkArgument(key.length() != 0, "Key must have non-zero length");
+        checkNotNull(hash, "Hash must not be null");
+
+        KeyValueListingProto.KeyValueEntry entry = new KeyValueListingProto.KeyValueEntry();
+        entry.key = key;
+        entry.hash = hash.getHash();
+        mEntries.add(entry);
+
+        return this;
+    }
+
+    /** Adds all pairs contained in a map, where the map is from key to hash. */
+    public KeyValueListingBuilder addAll(Map<String, ChunkHash> map) {
+        for (Entry<String, ChunkHash> entry : map.entrySet()) {
+            addPair(entry.getKey(), entry.getValue());
+        }
+
+        return this;
+    }
+
+    /** Returns a new listing containing all the pairs added so far. */
+    public KeyValueListingProto.KeyValueListing build() {
+        if (mEntries.size() == 0) {
+            return emptyListing();
+        }
+
+        KeyValueListingProto.KeyValueListing listing = new KeyValueListingProto.KeyValueListing();
+        listing.entries = new KeyValueListingProto.KeyValueEntry[mEntries.size()];
+        mEntries.toArray(listing.entries);
+        return listing;
+    }
+
+    /** Returns a new listing which does not contain any pairs. */
+    public static KeyValueListingProto.KeyValueListing emptyListing() {
+        KeyValueListingProto.KeyValueListing listing = new KeyValueListingProto.KeyValueListing();
+        listing.entries = KeyValueListingProto.KeyValueEntry.emptyArray();
+        return listing;
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
index e3df3c1..f67f100 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
@@ -19,6 +19,7 @@
 import java.io.Closeable;
 import java.io.IOException;
 import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
 
 /**
  * Accepts the plaintext bytes of decrypted chunks and writes them to some output. Also keeps track
@@ -30,7 +31,7 @@
      *
      * @return {@code this}, to allow use with try-with-resources
      */
-    DecryptedChunkOutput open() throws IOException;
+    DecryptedChunkOutput open() throws IOException, NoSuchAlgorithmException;
 
     /**
      * Writes the plaintext bytes of chunk to whatever output the implementation chooses. Also
@@ -43,12 +44,13 @@
      *     at index 0.
      * @param length The length in bytes of the plaintext contained in {@code plaintextBuffer}.
      */
-    void processChunk(byte[] plaintextBuffer, int length) throws IOException, InvalidKeyException;
+    void processChunk(byte[] plaintextBuffer, int length)
+            throws IOException, InvalidKeyException, NoSuchAlgorithmException;
 
     /**
      * Returns the message digest of all the chunks processed by {@link #processChunk}.
      *
      * <p>You must call {@link Closeable#close()} before calling this method.
      */
-    byte[] getDigest();
+    byte[] getDigest() throws NoSuchAlgorithmException;
 }
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutputTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutputTest.java
new file mode 100644
index 0000000..215e1cb
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutputTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.backup.encryption.kv;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.os.Debug;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.ChunkHasher;
+import com.android.server.backup.encryption.protos.nano.KeyValuePairProto;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Stream;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class DecryptedChunkKvOutputTest {
+    private static final String TEST_KEY_1 = "key_1";
+    private static final String TEST_KEY_2 = "key_2";
+    private static final byte[] TEST_VALUE_1 = {1, 2, 3};
+    private static final byte[] TEST_VALUE_2 = {10, 11, 12, 13};
+    private static final byte[] TEST_PAIR_1 = toByteArray(createPair(TEST_KEY_1, TEST_VALUE_1));
+    private static final byte[] TEST_PAIR_2 = toByteArray(createPair(TEST_KEY_2, TEST_VALUE_2));
+    private static final int TEST_BUFFER_SIZE = Math.max(TEST_PAIR_1.length, TEST_PAIR_2.length);
+
+    @Mock private ChunkHasher mChunkHasher;
+    private DecryptedChunkKvOutput mOutput;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mChunkHasher.computeHash(any()))
+                .thenAnswer(invocation -> fakeHash(invocation.getArgument(0)));
+        mOutput = new DecryptedChunkKvOutput(mChunkHasher);
+    }
+
+    @Test
+    public void open_returnsInstance() throws Exception {
+        assertThat(mOutput.open()).isEqualTo(mOutput);
+    }
+
+    @Test
+    public void processChunk_alreadyClosed_throws() throws Exception {
+        mOutput.open();
+        mOutput.close();
+
+        assertThrows(
+                IllegalStateException.class,
+                () -> mOutput.processChunk(TEST_PAIR_1, TEST_PAIR_1.length));
+    }
+
+    @Test
+    public void getDigest_beforeClose_throws() throws Exception {
+        // TODO: b/141356823 We should add a test which calls .open() here
+        assertThrows(IllegalStateException.class, () -> mOutput.getDigest());
+    }
+
+    @Test
+    public void getDigest_returnsDigestOfSortedHashes() throws Exception {
+        mOutput.open();
+        Debug.waitForDebugger();
+        mOutput.processChunk(Arrays.copyOf(TEST_PAIR_1, TEST_BUFFER_SIZE), TEST_PAIR_1.length);
+        mOutput.processChunk(Arrays.copyOf(TEST_PAIR_2, TEST_BUFFER_SIZE), TEST_PAIR_2.length);
+        mOutput.close();
+
+        byte[] actualDigest = mOutput.getDigest();
+
+        MessageDigest digest = MessageDigest.getInstance(DecryptedChunkKvOutput.DIGEST_ALGORITHM);
+        Stream.of(TEST_PAIR_1, TEST_PAIR_2)
+                .map(DecryptedChunkKvOutputTest::fakeHash)
+                .sorted(Comparator.naturalOrder())
+                .forEachOrdered(hash -> digest.update(hash.getHash()));
+        assertThat(actualDigest).isEqualTo(digest.digest());
+    }
+
+    @Test
+    public void getPairs_beforeClose_throws() throws Exception {
+        // TODO: b/141356823 We should add a test which calls .open() here
+        assertThrows(IllegalStateException.class, () -> mOutput.getPairs());
+    }
+
+    @Test
+    public void getPairs_returnsPairsSortedByKey() throws Exception {
+        mOutput.open();
+        // Write out of order to check that it sorts the chunks.
+        mOutput.processChunk(Arrays.copyOf(TEST_PAIR_2, TEST_BUFFER_SIZE), TEST_PAIR_2.length);
+        mOutput.processChunk(Arrays.copyOf(TEST_PAIR_1, TEST_BUFFER_SIZE), TEST_PAIR_1.length);
+        mOutput.close();
+
+        List<KeyValuePairProto.KeyValuePair> pairs = mOutput.getPairs();
+
+        assertThat(
+                        isInOrder(
+                                pairs,
+                                Comparator.comparing(
+                                        (KeyValuePairProto.KeyValuePair pair) -> pair.key)))
+                .isTrue();
+        assertThat(pairs).hasSize(2);
+        assertThat(pairs.get(0).key).isEqualTo(TEST_KEY_1);
+        assertThat(pairs.get(0).value).isEqualTo(TEST_VALUE_1);
+        assertThat(pairs.get(1).key).isEqualTo(TEST_KEY_2);
+        assertThat(pairs.get(1).value).isEqualTo(TEST_VALUE_2);
+    }
+
+    private static KeyValuePairProto.KeyValuePair createPair(String key, byte[] value) {
+        KeyValuePairProto.KeyValuePair pair = new KeyValuePairProto.KeyValuePair();
+        pair.key = key;
+        pair.value = value;
+        return pair;
+    }
+
+    private boolean isInOrder(
+            List<KeyValuePairProto.KeyValuePair> list,
+            Comparator<KeyValuePairProto.KeyValuePair> comparator) {
+        if (list.size() < 2) {
+            return true;
+        }
+
+        List<KeyValuePairProto.KeyValuePair> sortedList = new ArrayList<>(list);
+        Collections.sort(sortedList, comparator);
+        return list.equals(sortedList);
+    }
+
+    private static byte[] toByteArray(KeyValuePairProto.KeyValuePair nano) {
+        return KeyValuePairProto.KeyValuePair.toByteArray(nano);
+    }
+
+    private static ChunkHash fakeHash(byte[] data) {
+        return new ChunkHash(Arrays.copyOf(data, ChunkHash.HASH_LENGTH_BYTES));
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/KeyValueListingBuilderTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/KeyValueListingBuilderTest.java
new file mode 100644
index 0000000..acc6628
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/KeyValueListingBuilderTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.backup.encryption.kv;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.protos.nano.KeyValueListingProto;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Arrays;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class KeyValueListingBuilderTest {
+    private static final String TEST_KEY_1 = "test_key_1";
+    private static final String TEST_KEY_2 = "test_key_2";
+    private static final ChunkHash TEST_HASH_1 =
+            new ChunkHash(Arrays.copyOf(new byte[] {1, 2}, ChunkHash.HASH_LENGTH_BYTES));
+    private static final ChunkHash TEST_HASH_2 =
+            new ChunkHash(Arrays.copyOf(new byte[] {5, 6}, ChunkHash.HASH_LENGTH_BYTES));
+
+    private KeyValueListingBuilder mBuilder;
+
+    @Before
+    public void setUp() {
+        mBuilder = new KeyValueListingBuilder();
+    }
+
+    @Test
+    public void addPair_nullKey_throws() {
+        assertThrows(NullPointerException.class, () -> mBuilder.addPair(null, TEST_HASH_1));
+    }
+
+    @Test
+    public void addPair_emptyKey_throws() {
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.addPair("", TEST_HASH_1));
+    }
+
+    @Test
+    public void addPair_nullHash_throws() {
+        assertThrows(NullPointerException.class, () -> mBuilder.addPair(TEST_KEY_1, null));
+    }
+
+    @Test
+    public void build_noPairs_buildsEmptyListing() {
+        KeyValueListingProto.KeyValueListing listing = mBuilder.build();
+
+        assertThat(listing.entries).isEmpty();
+    }
+
+    @Test
+    public void build_returnsCorrectListing() {
+        mBuilder.addPair(TEST_KEY_1, TEST_HASH_1);
+
+        KeyValueListingProto.KeyValueListing listing = mBuilder.build();
+
+        assertThat(listing.entries.length).isEqualTo(1);
+        assertThat(listing.entries[0].key).isEqualTo(TEST_KEY_1);
+        assertThat(listing.entries[0].hash).isEqualTo(TEST_HASH_1.getHash());
+    }
+
+    @Test
+    public void addAll_addsAllPairsInMap() {
+        ImmutableMap<String, ChunkHash> pairs =
+                new ImmutableMap.Builder<String, ChunkHash>()
+                        .put(TEST_KEY_1, TEST_HASH_1)
+                        .put(TEST_KEY_2, TEST_HASH_2)
+                        .build();
+
+        mBuilder.addAll(pairs);
+        KeyValueListingProto.KeyValueListing listing = mBuilder.build();
+
+        assertThat(listing.entries.length).isEqualTo(2);
+        assertThat(listing.entries[0].key).isEqualTo(TEST_KEY_1);
+        assertThat(listing.entries[0].hash).isEqualTo(TEST_HASH_1.getHash());
+        assertThat(listing.entries[1].key).isEqualTo(TEST_KEY_2);
+        assertThat(listing.entries[1].hash).isEqualTo(TEST_HASH_2.getHash());
+    }
+
+    @Test
+    public void emptyListing_returnsListingWithoutAnyPairs() {
+        KeyValueListingProto.KeyValueListing emptyListing = KeyValueListingBuilder.emptyListing();
+        assertThat(emptyListing.entries).isEmpty();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index bd91333..1c0e0b3 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.HandlerThread;
+import android.os.Trace;
 import android.service.wallpaper.WallpaperService;
 import android.util.Log;
 import android.util.Size;
@@ -48,6 +49,7 @@
     private static final int DELAY_FINISH_RENDERING = 1000;
     private static final int INTERVAL_WAIT_FOR_RENDERING = 100;
     private static final int PATIENCE_WAIT_FOR_RENDERING = 10;
+    private static final boolean DEBUG = true;
     private HandlerThread mWorker;
 
     @Override
@@ -125,6 +127,10 @@
         @Override
         public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
             if (!mNeedTransition) return;
+            if (DEBUG) {
+                Log.d(TAG, "onAmbientModeChanged: inAmbient=" + inAmbientMode
+                        + ", duration=" + animationDuration);
+            }
             mWorker.getThreadHandler().post(
                     () -> mRenderer.updateAmbientMode(inAmbientMode, animationDuration));
             if (inAmbientMode && animationDuration == 0) {
@@ -184,17 +190,32 @@
 
         @Override
         public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
+            if (DEBUG) {
+                Log.d(TAG, "onSurfaceRedrawNeeded: mNeedRedraw=" + mNeedRedraw);
+            }
+
             mWorker.getThreadHandler().post(() -> {
                 if (mNeedRedraw) {
-                    preRender();
-                    requestRender();
-                    postRender();
+                    drawFrame();
                     mNeedRedraw = false;
                 }
             });
         }
 
         @Override
+        public void onVisibilityChanged(boolean visible) {
+            if (DEBUG) {
+                Log.d(TAG, "wallpaper visibility changes: " + visible);
+            }
+        }
+
+        private void drawFrame() {
+            preRender();
+            requestRender();
+            postRender();
+        }
+
+        @Override
         public void onStatePostChange() {
             // When back to home, we try to release EGL, which is preserved in lock screen or aod.
             if (mController.getState() == StatusBarState.SHADE) {
@@ -205,7 +226,9 @@
         @Override
         public void preRender() {
             // This method should only be invoked from worker thread.
+            Trace.beginSection("ImageWallpaper#preRender");
             preRenderInternal();
+            Trace.endSection();
         }
 
         private void preRenderInternal() {
@@ -240,7 +263,9 @@
         @Override
         public void requestRender() {
             // This method should only be invoked from worker thread.
+            Trace.beginSection("ImageWallpaper#requestRender");
             requestRenderInternal();
+            Trace.endSection();
         }
 
         private void requestRenderInternal() {
@@ -263,8 +288,10 @@
         @Override
         public void postRender() {
             // This method should only be invoked from worker thread.
+            Trace.beginSection("ImageWallpaper#postRender");
             notifyWaitingThread();
             scheduleFinishRendering();
+            Trace.endSection();
         }
 
         private void notifyWaitingThread() {
@@ -289,12 +316,14 @@
         }
 
         private void finishRendering() {
+            Trace.beginSection("ImageWallpaper#finishRendering");
             if (mEglHelper != null) {
                 mEglHelper.destroyEglSurface();
                 if (!needPreserveEglContext()) {
                     mEglHelper.destroyEglContext();
                 }
             }
+            Trace.endSection();
         }
 
         private boolean needPreserveEglContext() {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 0c4f051..b346a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -17,6 +17,7 @@
 package com.android.systemui.assist.ui;
 
 import static com.android.systemui.assist.AssistManager.DISMISS_REASON_INVOCATION_CANCELLED;
+import static com.android.systemui.assist.AssistManager.INVOCATION_TYPE_GESTURE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -24,6 +25,7 @@
 import android.content.Context;
 import android.graphics.PixelFormat;
 import android.metrics.LogMaker;
+import android.os.Build;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.Gravity;
@@ -40,6 +42,8 @@
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.assist.AssistManager;
 
+import java.util.Locale;
+
 /**
  * Default UiController implementation. Shows white edge lights along the bottom of the phone,
  * expanding from the corners to meet in the center.
@@ -50,6 +54,9 @@
 
     private static final long ANIM_DURATION_MS = 200;
 
+    private static final boolean VERBOSE = Build.TYPE.toLowerCase(Locale.ROOT).contains("debug")
+            || Build.TYPE.toLowerCase(Locale.ROOT).equals("eng");
+
     protected final FrameLayout mRoot;
     protected InvocationLightsView mInvocationLightsView;
 
@@ -114,6 +121,7 @@
     @Override // AssistManager.UiController
     public void onGestureCompletion(float velocity) {
         animateInvocationCompletion(AssistManager.INVOCATION_TYPE_GESTURE, velocity);
+        logInvocationProgressMetrics(INVOCATION_TYPE_GESTURE, 1, mInvocationInProgress);
     }
 
     @Override // AssistManager.UiController
@@ -127,16 +135,27 @@
         updateAssistHandleVisibility();
     }
 
-    protected static void logInvocationProgressMetrics(
+    protected void logInvocationProgressMetrics(
             int type, float progress, boolean invocationWasInProgress) {
         // Logs assistant invocation start.
+        if (progress == 1f) {
+            if (VERBOSE) {
+                Log.v(TAG, "Invocation complete: type=" + type);
+            }
+        }
         if (!invocationWasInProgress && progress > 0.f) {
+            if (VERBOSE) {
+                Log.v(TAG, "Invocation started: type=" + type);
+            }
             MetricsLogger.action(new LogMaker(MetricsEvent.ASSISTANT)
                     .setType(MetricsEvent.TYPE_ACTION)
                     .setSubtype(Dependency.get(AssistManager.class).toLoggingSubType(type)));
         }
         // Logs assistant invocation cancelled.
-        if (invocationWasInProgress && progress == 0f) {
+        if (!mInvocationAnimator.isRunning() && invocationWasInProgress && progress == 0f) {
+            if (VERBOSE) {
+                Log.v(TAG, "Invocation cancelled: type=" + type);
+            }
             MetricsLogger.action(new LogMaker(MetricsEvent.ASSISTANT)
                     .setType(MetricsEvent.TYPE_DISMISS)
                     .setSubtype(DISMISS_REASON_INVOCATION_CANCELLED));
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index bb8c7f1..4d3dc70 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -34,6 +34,7 @@
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
@@ -97,7 +98,8 @@
             DockManager dockManager) {
         boolean allowPulseTriggers = true;
         return new DozeTriggers(context, machine, host, alarmManager, config, params,
-                sensorManager, handler, wakeLock, allowPulseTriggers, dockManager);
+                sensorManager, handler, wakeLock, allowPulseTriggers, dockManager,
+                new ProximitySensor(context, sensorManager));
     }
 
     private DozeMachine.Part createDozeUi(Context context, DozeHost host, WakeLock wakeLock,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 67eefc5..dfd83a5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -42,9 +42,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.systemui.Dependency;
 import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.sensors.ProximitySensor;
@@ -147,8 +145,7 @@
                         false /* touchscreen */, mConfig.getWakeLockScreenDebounce()),
         };
 
-        mProximitySensor = new ProximitySensor(
-                context, sensorManager, Dependency.get(PluginManager.class));
+        mProximitySensor = new ProximitySensor(context, sensorManager);
 
         mProximitySensor.register(
                 proximityEvent -> mProxCallback.accept(!proximityEvent.getNear()));
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 80d4b63..8eed71c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -72,7 +72,6 @@
     private final AmbientDisplayConfiguration mConfig;
     private final DozeParameters mDozeParameters;
     private final AsyncSensorManager mSensorManager;
-    private final Handler mHandler;
     private final WakeLock mWakeLock;
     private final boolean mAllowPulseTriggers;
     private final UiModeManager mUiModeManager;
@@ -89,14 +88,14 @@
     public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
             AlarmManager alarmManager, AmbientDisplayConfiguration config,
             DozeParameters dozeParameters, AsyncSensorManager sensorManager, Handler handler,
-            WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager) {
+            WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager,
+            ProximitySensor proximitySensor) {
         mContext = context;
         mMachine = machine;
         mDozeHost = dozeHost;
         mConfig = config;
         mDozeParameters = dozeParameters;
         mSensorManager = sensorManager;
-        mHandler = handler;
         mWakeLock = wakeLock;
         mAllowPulseTriggers = allowPulseTriggers;
         mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters,
@@ -104,9 +103,7 @@
                 dozeParameters.getPolicy());
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
         mDockManager = dockManager;
-        mProxCheck = new ProximitySensor.ProximityCheck(
-                new ProximitySensor(mContext, mSensorManager, null),
-                mHandler);
+        mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, handler);
     }
 
     private void onNotification(Runnable onPulseSuppressedListener) {
@@ -182,7 +179,7 @@
             }
         } else {
             proximityCheckThenCall((result) -> {
-                if (result) {
+                if (result != null && result) {
                     // In pocket, drop event.
                     return;
                 }
@@ -271,7 +268,7 @@
 
         if (wake) {
             proximityCheckThenCall((result) -> {
-                if (result) {
+                if (result !=  null && result) {
                     // In pocket, drop event.
                     return;
                 }
@@ -380,7 +377,7 @@
 
         mPulsePending = true;
         proximityCheckThenCall((result) -> {
-            if (result) {
+            if (result != null && result) {
                 // in pocket, abort pulse
                 DozeLog.tracePulseDropped(mContext, "inPocket");
                 mPulsePending = false;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index c9c6a0c..8eeb629 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -665,8 +665,7 @@
                         // Take an "interactive" bugreport.
                         MetricsLogger.action(mContext,
                                 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
-                        ActivityManager.getService().requestBugReport(
-                                ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
+                        ActivityManager.getService().requestInteractiveBugReport();
                     } catch (RemoteException e) {
                     }
                 }
@@ -683,8 +682,7 @@
             try {
                 // Take a "full" bugreport.
                 MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
-                ActivityManager.getService().requestBugReport(
-                        ActivityManager.BUGREPORT_OPTION_FULL);
+                ActivityManager.getService().requestFullBugReport();
             } catch (RemoteException e) {
             }
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
index aac721e..6b5a780 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
@@ -63,6 +63,7 @@
     // Below two constants make drawing at low priority, so other things can preempt our drawing.
     private static final int EGL_CONTEXT_PRIORITY_LEVEL_IMG = 0x3100;
     private static final int EGL_CONTEXT_PRIORITY_LOW_IMG = 0x3103;
+    private static final boolean DEBUG = true;
 
     private EGLDisplay mEglDisplay;
     private EGLConfig mEglConfig;
@@ -146,6 +147,10 @@
      * @return true if EglSurface is ready.
      */
     public boolean createEglSurface(SurfaceHolder surfaceHolder) {
+        if (DEBUG) {
+            Log.d(TAG, "createEglSurface start");
+        }
+
         if (hasEglDisplay()) {
             mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null, 0);
         } else {
@@ -163,6 +168,9 @@
             return false;
         }
 
+        if (DEBUG) {
+            Log.d(TAG, "createEglSurface done");
+        }
         return true;
     }
 
@@ -190,6 +198,10 @@
      * @return true if EglContext is ready.
      */
     public boolean createEglContext() {
+        if (DEBUG) {
+            Log.d(TAG, "createEglContext start");
+        }
+
         int[] attrib_list = new int[] {EGL_CONTEXT_CLIENT_VERSION, 2,
                 EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_LOW_IMG, EGL_NONE};
         if (hasEglDisplay()) {
@@ -203,6 +215,10 @@
             Log.w(TAG, "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError()));
             return false;
         }
+
+        if (DEBUG) {
+            Log.d(TAG, "createEglContext done");
+        }
         return true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java
index b154e66..99c55f1 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.util.Log;
 
 import com.android.systemui.Interpolators;
 
@@ -30,6 +31,7 @@
     private static final String TAG = ImageRevealHelper.class.getSimpleName();
     private static final float MAX_REVEAL = 0f;
     private static final float MIN_REVEAL = 1f;
+    private static final boolean DEBUG = true;
 
     private final ValueAnimator mAnimator;
     private final RevealStateListener mRevealListener;
@@ -57,6 +59,9 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 if (!mIsCanceled && mRevealListener != null) {
+                    if (DEBUG) {
+                        Log.d(TAG, "transition end");
+                    }
                     mRevealListener.onRevealEnd();
                 }
                 mIsCanceled = false;
@@ -65,6 +70,9 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 if (mRevealListener != null) {
+                    if (DEBUG) {
+                        Log.d(TAG, "transition start");
+                    }
                     mRevealListener.onRevealStart(true /* animate */);
                 }
             }
@@ -82,6 +90,9 @@
     }
 
     void updateAwake(boolean awake, long duration) {
+        if (DEBUG) {
+            Log.d(TAG, "updateAwake: awake=" + awake + ", duration=" + duration);
+        }
         mAwake = awake;
         mAnimator.setDuration(duration);
         if (duration == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index 2960634..a8371e3 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -46,6 +46,7 @@
     private static final String TAG = ImageWallpaperRenderer.class.getSimpleName();
     private static final float SCALE_VIEWPORT_MIN = 1f;
     private static final float SCALE_VIEWPORT_MAX = 1.1f;
+    private static final boolean DEBUG = true;
 
     private final WallpaperManager mWallpaperManager;
     private final ImageGLProgram mProgram;
@@ -107,6 +108,9 @@
     }
 
     private boolean loadBitmap() {
+        if (DEBUG) {
+            Log.d(TAG, "loadBitmap: mBitmap=" + mBitmap);
+        }
         if (mWallpaperManager != null && mBitmap == null) {
             mBitmap = mWallpaperManager.getBitmap();
             mWallpaperManager.forgetLoadedWallpaper();
@@ -119,6 +123,9 @@
                 mSurfaceSize.set(0, 0, surfaceWidth, surfaceHeight);
             }
         }
+        if (DEBUG) {
+            Log.d(TAG, "loadBitmap done");
+        }
         return mBitmap != null;
     }
 
@@ -223,6 +230,7 @@
         out.print(prefix); out.print("mXOffset="); out.print(mXOffset);
         out.print(prefix); out.print("mYOffset="); out.print(mYOffset);
         out.print(prefix); out.print("threshold="); out.print(mImageProcessHelper.getThreshold());
+        out.print(prefix); out.print("mReveal="); out.print(mImageRevealHelper.getReveal());
         mWallpaper.dump(prefix, fd, out, args);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 00b764b..d9de59e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -66,6 +66,9 @@
     }
 
     private fun updateListeningState() {
+        if (pickupSensor == null) {
+            return
+        }
         val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible &&
                 !statusBarStateController.isDozing
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index c48bdde..cce5bca 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -24,8 +24,8 @@
 import android.os.Handler;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
-import com.android.systemui.shared.plugins.PluginManager;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -47,7 +47,7 @@
     private final float mMaxRange;
     private List<ProximitySensorListener> mListeners = new ArrayList<>();
     private String mTag = null;
-    private ProximityEvent mLastEvent;
+    @VisibleForTesting ProximityEvent mLastEvent;
     private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;
     private boolean mPaused;
     private boolean mRegistered;
@@ -64,8 +64,7 @@
     };
 
     @Inject
-    public ProximitySensor(
-            Context context, AsyncSensorManager sensorManager, PluginManager pluginManager) {
+    public ProximitySensor(Context context, AsyncSensorManager sensorManager) {
         mSensorManager = sensorManager;
         Sensor sensor = findBrightnessSensor(context);
 
@@ -146,17 +145,17 @@
             return false;
         }
 
-        logDebug("Using brightness sensor? " + mUsingBrightnessSensor);
         mListeners.add(listener);
         registerInternal();
 
         return true;
     }
 
-    private void registerInternal() {
+    protected void registerInternal() {
         if (mRegistered || mPaused || mListeners.isEmpty()) {
             return;
         }
+        logDebug("Using brightness sensor? " + mUsingBrightnessSensor);
         logDebug("Registering sensor listener");
         mRegistered = true;
         mSensorManager.registerListener(mSensorEventListener, mSensor, mSensorDelay);
@@ -175,7 +174,7 @@
         }
     }
 
-    private void unregisterInternal() {
+    protected void unregisterInternal() {
         if (!mRegistered) {
             return;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index f7cd696..b0e3969 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -41,7 +41,9 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.FakeProximitySensor;
 import com.android.systemui.util.sensors.FakeSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.wakelock.WakeLock;
 import com.android.systemui.util.wakelock.WakeLockFake;
 
@@ -60,6 +62,7 @@
     private FakeSensorManager mSensors;
     private Sensor mTapSensor;
     private DockManager mDockManagerFake;
+    private FakeProximitySensor mProximitySensor;
 
     @BeforeClass
     public static void setupSuite() {
@@ -80,10 +83,11 @@
         mDockManagerFake = mock(DockManager.class);
         AsyncSensorManager asyncSensorManager =
                 new AsyncSensorManager(mSensors, null, new Handler());
+        mProximitySensor = new FakeProximitySensor(getContext(), asyncSensorManager);
 
         mTriggers = new DozeTriggers(mContext, mMachine, mHost, alarmManager, config, parameters,
                 asyncSensorManager, Handler.createAsync(Looper.myLooper()), wakeLock, true,
-                mDockManagerFake);
+                mDockManagerFake, mProximitySensor);
         waitForSensorManager();
     }
 
@@ -95,15 +99,17 @@
         mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
         clearInvocations(mMachine);
 
+        mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(true, 1));
         mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */);
-        mSensors.getFakeProximitySensor().sendProximityResult(false); /* Near */
+        mProximitySensor.alertListeners();
 
         verify(mMachine, never()).requestState(any());
         verify(mMachine, never()).requestPulse(anyInt());
 
         mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */);
         waitForSensorManager();
-        mSensors.getFakeProximitySensor().sendProximityResult(true); /* Far */
+        mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(false, 2));
+        mProximitySensor.alertListeners();
 
         verify(mMachine).requestPulse(anyInt());
     }
@@ -139,6 +145,14 @@
         verify(mDockManagerFake).removeListener(any());
     }
 
+    @Test
+    public void testProximitySensorNotAvailablel() {
+        mProximitySensor.setSensorAvailable(false);
+        mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, 100, 100, null);
+        mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, 100, 100, new float[]{1});
+        mTriggers.onSensor(DozeLog.REASON_SENSOR_TAP, 100, 100, null);
+    }
+
     private void waitForSensorManager() {
         TestableLooper.get(this).processAllMessages();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
new file mode 100644
index 0000000..d7df96d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
@@ -0,0 +1,53 @@
+/*
+ * 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.util.sensors;
+
+import android.content.Context;
+
+public class FakeProximitySensor extends ProximitySensor {
+    private boolean mAvailable;
+    private boolean mPaused;
+
+    public FakeProximitySensor(Context context, AsyncSensorManager sensorManager) {
+        super(context, sensorManager);
+
+        mAvailable = true;
+    }
+
+    public void setSensorAvailable(boolean available) {
+        mAvailable = available;
+    }
+
+    public void setLastEvent(ProximityEvent event) {
+        mLastEvent = event;
+    }
+
+    @Override
+    public boolean getSensorAvailable() {
+        return mAvailable;
+    }
+
+    @Override
+    protected void registerInternal() {
+        // no-op
+    }
+
+    @Override
+    protected void unregisterInternal() {
+        // no-op
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
index 6d13408..fa943e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
@@ -47,7 +47,7 @@
         AsyncSensorManager asyncSensorManager = new AsyncSensorManager(
                 sensorManager, null, new Handler());
         mFakeProximitySensor = sensorManager.getFakeProximitySensor();
-        mProximitySensor = new ProximitySensor(getContext(), asyncSensorManager, null);
+        mProximitySensor = new ProximitySensor(getContext(), asyncSensorManager);
     }
 
     @Test
diff --git a/services/Android.bp b/services/Android.bp
index 6953e86..60dd895 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -47,6 +47,11 @@
         "compat-changeid-annotation-processor",
     ],
 
+    required: [
+      // Required by services.backup
+      "BackupEncryption",
+    ],
+
     // Uncomment to enable output of certain warnings (deprecated, unchecked)
     //javacflags: ["-Xlint"],
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index d46c626..058afd3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1016,18 +1016,23 @@
 
     int runBugReport(PrintWriter pw) throws RemoteException {
         String opt;
-        int bugreportType = ActivityManager.BUGREPORT_OPTION_FULL;
+        boolean fullBugreport = true;
         while ((opt=getNextOption()) != null) {
             if (opt.equals("--progress")) {
-                bugreportType = ActivityManager.BUGREPORT_OPTION_INTERACTIVE;
+                fullBugreport = false;
+                mInterface.requestInteractiveBugReport();
             } else if (opt.equals("--telephony")) {
-                bugreportType = ActivityManager.BUGREPORT_OPTION_TELEPHONY;
+                fullBugreport = false;
+                // no title and description specified
+                mInterface.requestTelephonyBugReport("" /* no title */, "" /* no descriptions */);
             } else {
                 getErrPrintWriter().println("Error: Unknown option: " + opt);
                 return -1;
             }
         }
-        mInterface.requestBugReport(bugreportType);
+        if (fullBugreport) {
+            mInterface.requestFullBugReport();
+        }
         pw.println("Your lovely bug report is being created; please be patient.");
         return 0;
     }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 159e5b8..f866314 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -807,7 +807,12 @@
         public void binderDied() {
             synchronized (AppOpsService.this) {
                 for (int i=mStartedOps.size()-1; i>=0; i--) {
-                    finishOperationLocked(mStartedOps.get(i), /*finishNested*/ true);
+                    final Op op = mStartedOps.get(i);
+                    finishOperationLocked(op, /*finishNested*/ true);
+                    if (op.startNesting <= 0) {
+                        scheduleOpActiveChangedIfNeededLocked(op.op, op.uidState.uid,
+                                op.packageName, false);
+                    }
                 }
                 mClients.remove(mAppToken);
             }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index e8198b9..6010b1dc 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -57,6 +57,13 @@
     private final @NonNull AudioService mAudioService;
     private final @NonNull Context mContext;
 
+    /** Forced device usage for communications sent to AudioSystem */
+    private int mForcedUseForComm;
+    /**
+     * Externally reported force device usage state returned by getters: always consistent
+     * with requests by setters */
+    private int mForcedUseForCommExt;
+
     // Manages all connected devices, only ever accessed on the message loop
     private final AudioDeviceInventory mDeviceInventory;
     // Manages notifications to BT service
@@ -64,34 +71,24 @@
 
 
     //-------------------------------------------------------------------
-    /**
-     * Lock to guard:
-     * - any changes to the message queue: enqueueing or removing any message
-     * - state of A2DP enabled
-     * - force use for communication + SCO changes
-     */
-    private final Object mDeviceBrokerLock = new Object();
-
-    @GuardedBy("mDeviceBrokerLock")
+    // we use a different lock than mDeviceStateLock so as not to create
+    // lock contention between enqueueing a message and handling them
+    private static final Object sLastDeviceConnectionMsgTimeLock = new Object();
+    @GuardedBy("sLastDeviceConnectionMsgTimeLock")
     private static long sLastDeviceConnectMsgTime = 0;
 
+    // General lock to be taken whenever the state of the audio devices is to be checked or changed
+    private final Object mDeviceStateLock = new Object();
 
-    /** Request to override default use of A2DP for media */
-    @GuardedBy("mDeviceBrokerLock")
+    // Request to override default use of A2DP for media.
+    @GuardedBy("mDeviceStateLock")
     private boolean mBluetoothA2dpEnabled;
 
-    /** Forced device usage for communications sent to AudioSystem */
-    @GuardedBy("mDeviceBrokerLock")
-    private int mForcedUseForComm;
-    /**
-     * Externally reported force device usage state returned by getters: always consistent
-     * with requests by setters */
-    @GuardedBy("mDeviceBrokerLock")
-    private int mForcedUseForCommExt;
-
+    // lock always taken when accessing AudioService.mSetModeDeathHandlers
+    // TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
+    /*package*/ final Object mSetModeLock = new Object();
 
     //-------------------------------------------------------------------
-    /** Normal constructor used by AudioService */
     /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
         mContext = context;
         mAudioService = service;
@@ -130,37 +127,36 @@
     // All post* methods are asynchronous
 
     /*package*/ void onSystemReady() {
-        mBtHelper.onSystemReady();
+        synchronized (mSetModeLock) {
+            synchronized (mDeviceStateLock) {
+                mBtHelper.onSystemReady();
+            }
+        }
     }
 
     /*package*/ void onAudioServerDied() {
         // Restore forced usage for communications and record
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             AudioSystem.setParameters(
                     "BT_SCO=" + (mForcedUseForComm == AudioSystem.FORCE_BT_SCO ? "on" : "off"));
             onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied");
             onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied");
-
-            // restore devices
-            sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE);
         }
+        // restore devices
+        sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE);
     }
 
     /*package*/ void setForceUse_Async(int useCase, int config, String eventSource) {
-        synchronized (mDeviceBrokerLock) {
-            sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
-                    useCase, config, eventSource);
-        }
+        sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+                useCase, config, eventSource);
     }
 
     /*package*/ void toggleHdmiIfConnected_Async() {
-        synchronized (mDeviceBrokerLock) {
-            sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE);
-        }
+        sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE);
     }
 
     /*package*/ void disconnectAllBluetoothProfiles() {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             mBtHelper.disconnectAllBluetoothProfiles();
         }
     }
@@ -172,11 +168,15 @@
      * @param intent
      */
     /*package*/ void receiveBtEvent(@NonNull Intent intent) {
-        mBtHelper.receiveBtEvent(intent);
+        synchronized (mSetModeLock) {
+            synchronized (mDeviceStateLock) {
+                mBtHelper.receiveBtEvent(intent);
+            }
+        }
     }
 
     /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             if (mBluetoothA2dpEnabled == on) {
                 return;
             }
@@ -196,7 +196,7 @@
      * @return true if speakerphone state changed
      */
     /*package*/ boolean setSpeakerphoneOn(boolean on, String eventSource) {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             final boolean wasOn = isSpeakerphoneOn();
             if (on) {
                 if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
@@ -214,7 +214,7 @@
     }
 
     /*package*/ boolean isSpeakerphoneOn() {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
         }
     }
@@ -223,7 +223,9 @@
             @AudioService.ConnectionState int state, String address, String name,
             String caller) {
         //TODO move logging here just like in setBluetooth* methods
-        mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
+        synchronized (mDeviceStateLock) {
+            mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
+        }
     }
 
     private static final class BtDeviceConnectionInfo {
@@ -257,24 +259,27 @@
         final BtDeviceConnectionInfo info = new BtDeviceConnectionInfo(device, state, profile,
                 suppressNoisyIntent, a2dpVolume);
 
-        synchronized (mDeviceBrokerLock) {
-            // when receiving a request to change the connection state of a device, this last
-            // request is the source of truth, so cancel all previous requests
-            mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
-                    device);
-            mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION,
-                    device);
-            mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
-                    device);
-            mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
-                    device);
+        // when receiving a request to change the connection state of a device, this last request
+        // is the source of truth, so cancel all previous requests
+        removeAllA2dpConnectionEvents(device);
 
-            sendLMsgNoDelay(
-                    state == BluetoothProfile.STATE_CONNECTED
-                            ? MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION
-                            : MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
-                    SENDMSG_QUEUE, info);
-        }
+        sendLMsgNoDelay(
+                state == BluetoothProfile.STATE_CONNECTED
+                        ? MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION
+                        : MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
+                SENDMSG_QUEUE, info);
+    }
+
+    /** remove all previously scheduled connection and disconnection events for the given device */
+    private void removeAllA2dpConnectionEvents(@NonNull BluetoothDevice device) {
+        mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
+                device);
+        mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION,
+                device);
+        mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
+                device);
+        mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
+                device);
     }
 
     private static final class HearingAidDeviceConnectionInfo {
@@ -300,27 +305,25 @@
             boolean suppressNoisyIntent, int musicDevice, @NonNull String eventSource) {
         final HearingAidDeviceConnectionInfo info = new HearingAidDeviceConnectionInfo(
                 device, state, suppressNoisyIntent, musicDevice, eventSource);
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
-        }
+        sendLMsgNoDelay(MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
     }
 
     // never called by system components
     /*package*/ void setBluetoothScoOnByApp(boolean on) {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
         }
     }
 
     /*package*/ boolean isBluetoothScoOnForApp() {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO;
         }
     }
 
     /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
         //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             if (on) {
                 // do not accept SCO ON if SCO audio is not connected
                 if (!mBtHelper.isBluetoothScoOn()) {
@@ -343,55 +346,58 @@
     }
 
     /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
-        return mDeviceInventory.startWatchingRoutes(observer);
-
+        synchronized (mDeviceStateLock) {
+            return mDeviceInventory.startWatchingRoutes(observer);
+        }
     }
 
     /*package*/ AudioRoutesInfo getCurAudioRoutes() {
-        return mDeviceInventory.getCurAudioRoutes();
+        synchronized (mDeviceStateLock) {
+            return mDeviceInventory.getCurAudioRoutes();
+        }
     }
 
     /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
-        return mBtHelper.isAvrcpAbsoluteVolumeSupported();
+        synchronized (mDeviceStateLock) {
+            return mBtHelper.isAvrcpAbsoluteVolumeSupported();
+        }
     }
 
     /*package*/ boolean isBluetoothA2dpOn() {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             return mBluetoothA2dpEnabled;
         }
     }
 
     /*package*/ void postSetAvrcpAbsoluteVolumeIndex(int index) {
-        synchronized (mDeviceBrokerLock) {
-            sendIMsgNoDelay(MSG_I_SET_AVRCP_ABSOLUTE_VOLUME, SENDMSG_REPLACE, index);
-        }
+        sendIMsgNoDelay(MSG_I_SET_AVRCP_ABSOLUTE_VOLUME, SENDMSG_REPLACE, index);
     }
 
     /*package*/ void postSetHearingAidVolumeIndex(int index, int streamType) {
-        synchronized (mDeviceBrokerLock) {
-            sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
-        }
+        sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
     }
 
     /*package*/ void postDisconnectBluetoothSco(int exceptPid) {
-        synchronized (mDeviceBrokerLock) {
-            sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid);
-        }
+        sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid);
     }
 
     /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
+        sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
+    }
+
+    @GuardedBy("mSetModeLock")
+    /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
+                @NonNull String eventSource) {
+        synchronized (mDeviceStateLock) {
+            mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
         }
     }
 
-    /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
-                @NonNull String eventSource) {
-        mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
-    }
-
+    @GuardedBy("mSetModeLock")
     /*package*/ void stopBluetoothScoForClient_Sync(IBinder cb, @NonNull String eventSource) {
-        mBtHelper.stopBluetoothScoForClient(cb, eventSource);
+        synchronized (mDeviceStateLock) {
+            mBtHelper.stopBluetoothScoForClient(cb, eventSource);
+        }
     }
 
     //---------------------------------------------------------------------
@@ -454,109 +460,77 @@
     //---------------------------------------------------------------------
     // Message handling on behalf of helper classes
     /*package*/ void postBroadcastScoConnectionState(int state) {
-        synchronized (mDeviceBrokerLock) {
-            sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
-        }
+        sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
     }
 
     /*package*/ void postBroadcastBecomingNoisy() {
-        synchronized (mDeviceBrokerLock) {
-            sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
-        }
+        sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
     }
 
     /*package*/ void postA2dpSinkConnection(@AudioService.BtProfileConnectionState int state,
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
-        synchronized (mDeviceBrokerLock) {
-            sendILMsg(state == BluetoothA2dp.STATE_CONNECTED
-                            ? MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED
-                            : MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
-                    SENDMSG_QUEUE,
-                    state, btDeviceInfo, delay);
-        }
+        sendILMsg(state == BluetoothA2dp.STATE_CONNECTED
+                        ? MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED
+                        : MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
+                SENDMSG_QUEUE,
+                state, btDeviceInfo, delay);
     }
 
     /*package*/ void postA2dpSourceConnection(@AudioService.BtProfileConnectionState int state,
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
-        synchronized (mDeviceBrokerLock) {
-            sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
-                    state, btDeviceInfo, delay);
-        }
+        sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
+                state, btDeviceInfo, delay);
     }
 
     /*package*/ void postSetWiredDeviceConnectionState(
             AudioDeviceInventory.WiredDeviceConnectionState connectionState, int delay) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE,
-                    connectionState, delay);
-        }
+        sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE, connectionState, delay);
     }
 
     /*package*/ void postSetHearingAidConnectionState(
             @AudioService.BtProfileConnectionState int state,
             @NonNull BluetoothDevice device, int delay) {
-        synchronized (mDeviceBrokerLock) {
-            sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE,
-                    state,
-                    device,
-                    delay);
-        }
+        sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE,
+                state,
+                device,
+                delay);
     }
 
     /*package*/ void postDisconnectA2dp() {
-        synchronized (mDeviceBrokerLock) {
-            sendMsgNoDelay(MSG_DISCONNECT_A2DP, SENDMSG_QUEUE);
-        }
+        sendMsgNoDelay(MSG_DISCONNECT_A2DP, SENDMSG_QUEUE);
     }
 
     /*package*/ void postDisconnectA2dpSink() {
-        synchronized (mDeviceBrokerLock) {
-            sendMsgNoDelay(MSG_DISCONNECT_A2DP_SINK, SENDMSG_QUEUE);
-        }
+        sendMsgNoDelay(MSG_DISCONNECT_A2DP_SINK, SENDMSG_QUEUE);
     }
 
     /*package*/ void postDisconnectHearingAid() {
-        synchronized (mDeviceBrokerLock) {
-            sendMsgNoDelay(MSG_DISCONNECT_BT_HEARING_AID, SENDMSG_QUEUE);
-        }
+        sendMsgNoDelay(MSG_DISCONNECT_BT_HEARING_AID, SENDMSG_QUEUE);
     }
 
     /*package*/ void postDisconnectHeadset() {
-        synchronized (mDeviceBrokerLock) {
-            sendMsgNoDelay(MSG_DISCONNECT_BT_HEADSET, SENDMSG_QUEUE);
-        }
+        sendMsgNoDelay(MSG_DISCONNECT_BT_HEADSET, SENDMSG_QUEUE);
     }
 
     /*package*/ void postBtA2dpProfileConnected(BluetoothA2dp a2dpProfile) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP, SENDMSG_QUEUE, a2dpProfile);
-        }
+        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP, SENDMSG_QUEUE, a2dpProfile);
     }
 
     /*package*/ void postBtA2dpSinkProfileConnected(BluetoothProfile profile) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK, SENDMSG_QUEUE, profile);
-        }
+        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK, SENDMSG_QUEUE, profile);
     }
 
     /*package*/ void postBtHeasetProfileConnected(BluetoothHeadset headsetProfile) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET, SENDMSG_QUEUE,
-                    headsetProfile);
-        }
+        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET, SENDMSG_QUEUE, headsetProfile);
     }
 
     /*package*/ void postBtHearingAidProfileConnected(BluetoothHearingAid hearingAidProfile) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID, SENDMSG_QUEUE,
-                    hearingAidProfile);
-        }
+        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID, SENDMSG_QUEUE,
+                hearingAidProfile);
     }
 
     /*package*/ void postScoClientDied(Object obj) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
-        }
+        sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
     }
 
     //---------------------------------------------------------------------
@@ -571,7 +545,7 @@
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
                 .append(Binder.getCallingPid()).append(" src:").append(source).toString();
 
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             mBluetoothA2dpEnabled = on;
             mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
             onSetForceUse(
@@ -583,85 +557,71 @@
 
     /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
                                                        String deviceName) {
-        return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
+        synchronized (mDeviceStateLock) {
+            return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
+        }
     }
 
     /*package*/ void postSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state,
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
         final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
-        synchronized (mDeviceBrokerLock) {
-            sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state,
-                    btDeviceInfo);
-        }
+        sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state,
+                btDeviceInfo);
     }
 
     /*package*/ void handleFailureToConnectToBtHeadsetService(int delay) {
-        synchronized (mDeviceBrokerLock) {
-            sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay);
-        }
+        sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay);
     }
 
     /*package*/ void handleCancelFailureToConnectToBtHeadsetService() {
-        synchronized (mDeviceBrokerLock) {
-            mBrokerHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
-        }
+        mBrokerHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
     }
 
     /*package*/ void postReportNewRoutes() {
-        synchronized (mDeviceBrokerLock) {
-            sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
-        }
+        sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
     }
 
     /*package*/ void cancelA2dpDockTimeout() {
-        synchronized (mDeviceBrokerLock) {
-            mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
-        }
+        mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
     }
 
-    // FIXME: used by?
     /*package*/ void postA2dpActiveDeviceChange(
                     @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
-        }
+        sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
     }
 
     /*package*/ boolean hasScheduledA2dpDockTimeout() {
-        synchronized (mDeviceBrokerLock) {
-            return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
-        }
+        return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
     }
 
     // must be called synchronized on mConnectedDevices
     /*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) {
-        synchronized (mDeviceBrokerLock) {
-            return (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
-                    new BtHelper.BluetoothA2dpDeviceInfo(btDevice))
-                    || mBrokerHandler.hasMessages(
-                            MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
-                            new BtHelper.BluetoothA2dpDeviceInfo(btDevice)));
-        }
+        return (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
+                        new BtHelper.BluetoothA2dpDeviceInfo(btDevice))
+                || mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
+                        new BtHelper.BluetoothA2dpDeviceInfo(btDevice)));
     }
 
     /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) {
-        synchronized (mDeviceBrokerLock) {
-            sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
-        }
+        sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
     }
 
     /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
-        mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
+        synchronized (mDeviceStateLock) {
+            mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
+        }
     }
 
     /*package*/ boolean getBluetoothA2dpEnabled() {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             return mBluetoothA2dpEnabled;
         }
     }
 
     /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
-        return mBtHelper.getA2dpCodec(device);
+        synchronized (mDeviceStateLock) {
+            return mBtHelper.getA2dpCodec(device);
+        }
     }
 
     /*package*/ void dump(PrintWriter pw, String prefix) {
@@ -749,50 +709,68 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_RESTORE_DEVICES:
-                    mDeviceInventory.onRestoreDevices();
-                    mBtHelper.onAudioServerDiedRestoreA2dp();
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onRestoreDevices();
+                        mBtHelper.onAudioServerDiedRestoreA2dp();
+                    }
                     break;
                 case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
-                    mDeviceInventory.onSetWiredDeviceConnectionState(
-                            (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onSetWiredDeviceConnectionState(
+                                (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj);
+                    }
                     break;
                 case MSG_I_BROADCAST_BT_CONNECTION_STATE:
-                    mBtHelper.onBroadcastScoConnectionState(msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.onBroadcastScoConnectionState(msg.arg1);
+                    }
                     break;
                 case MSG_IIL_SET_FORCE_USE: // intended fall-through
                 case MSG_IIL_SET_FORCE_BT_A2DP_USE:
                     onSetForceUse(msg.arg1, msg.arg2, (String) msg.obj);
                     break;
                 case MSG_REPORT_NEW_ROUTES:
-                    mDeviceInventory.onReportNewRoutes();
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onReportNewRoutes();
+                    }
                     break;
                 case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
                 case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
-                    mDeviceInventory.onSetA2dpSinkConnectionState(
-                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onSetA2dpSinkConnectionState(
+                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+                    }
                     break;
                 case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
-                    mDeviceInventory.onSetA2dpSourceConnectionState(
-                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onSetA2dpSourceConnectionState(
+                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+                    }
                     break;
                 case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
-                    mDeviceInventory.onSetHearingAidConnectionState(
-                            (BluetoothDevice) msg.obj, msg.arg1,
-                            mAudioService.getHearingAidStreamType());
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onSetHearingAidConnectionState(
+                                (BluetoothDevice) msg.obj, msg.arg1,
+                                mAudioService.getHearingAidStreamType());
+                    }
                     break;
                 case MSG_BT_HEADSET_CNCT_FAILED:
-                    mBtHelper.resetBluetoothSco();
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            mBtHelper.resetBluetoothSco();
+                        }
+                    }
                     break;
                 case MSG_IL_BTA2DP_DOCK_TIMEOUT:
                     // msg.obj  == address of BTA2DP device
-                    mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
+                    }
                     break;
                 case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
                     final int a2dpCodec;
                     final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
-                    synchronized (mDeviceBrokerLock) {
-                        // FIXME why isn't the codec coming with the request? lock shouldn't be
-                        // needed here
+                    synchronized (mDeviceStateLock) {
                         a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
                         mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
                                 new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec),
@@ -803,48 +781,84 @@
                     onSendBecomingNoisyIntent();
                     break;
                 case MSG_II_SET_HEARING_AID_VOLUME:
-                    mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
+                    }
                     break;
                 case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME:
-                    mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
+                    }
                     break;
                 case MSG_I_DISCONNECT_BT_SCO:
-                    mBtHelper.disconnectBluetoothSco(msg.arg1);
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            mBtHelper.disconnectBluetoothSco(msg.arg1);
+                        }
+                    }
                     break;
                 case MSG_L_SCOCLIENT_DIED:
-                    mBtHelper.scoClientDied(msg.obj);
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            mBtHelper.scoClientDied(msg.obj);
+                        }
+                    }
                     break;
                 case MSG_TOGGLE_HDMI:
-                    mDeviceInventory.onToggleHdmi();
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onToggleHdmi();
+                    }
                     break;
                 case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
-                    mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
-                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj,
-                            BtHelper.EVENT_ACTIVE_DEVICE_CHANGE);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
+                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj,
+                                 BtHelper.EVENT_ACTIVE_DEVICE_CHANGE);
+                    }
                     break;
                 case MSG_DISCONNECT_A2DP:
-                    mDeviceInventory.disconnectA2dp();
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.disconnectA2dp();
+                    }
                     break;
                 case MSG_DISCONNECT_A2DP_SINK:
-                    mDeviceInventory.disconnectA2dpSink();
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.disconnectA2dpSink();
+                    }
                     break;
                 case MSG_DISCONNECT_BT_HEARING_AID:
-                    mDeviceInventory.disconnectHearingAid();
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.disconnectHearingAid();
+                    }
                     break;
                 case MSG_DISCONNECT_BT_HEADSET:
-                    mBtHelper.disconnectHeadset();
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            mBtHelper.disconnectHeadset();
+                        }
+                    }
                     break;
                 case MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP:
-                    mBtHelper.onA2dpProfileConnected((BluetoothA2dp) msg.obj);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.onA2dpProfileConnected((BluetoothA2dp) msg.obj);
+                    }
                     break;
                 case MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK:
-                    mBtHelper.onA2dpSinkProfileConnected((BluetoothProfile) msg.obj);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.onA2dpSinkProfileConnected((BluetoothProfile) msg.obj);
+                    }
                     break;
                 case MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID:
-                    mBtHelper.onHearingAidProfileConnected((BluetoothHearingAid) msg.obj);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.onHearingAidProfileConnected((BluetoothHearingAid) msg.obj);
+                    }
                     break;
                 case MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET:
-                    mBtHelper.onHeadsetProfileConnected((BluetoothHeadset) msg.obj);
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            mBtHelper.onHeadsetProfileConnected((BluetoothHeadset) msg.obj);
+                        }
+                    }
                     break;
                 case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION:
                 case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION: {
@@ -857,9 +871,11 @@
                                     + " addr=" + info.mDevice.getAddress()
                                     + " prof=" + info.mProfile + " supprNoisy=" + info.mSupprNoisy
                                     + " vol=" + info.mVolume)).printLog(TAG));
-                    mDeviceInventory.setBluetoothA2dpDeviceConnectionState(
-                            info.mDevice, info.mState, info.mProfile, info.mSupprNoisy,
-                            AudioSystem.DEVICE_NONE, info.mVolume);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.setBluetoothA2dpDeviceConnectionState(
+                                info.mDevice, info.mState, info.mProfile, info.mSupprNoisy,
+                                AudioSystem.DEVICE_NONE, info.mVolume);
+                    }
                 } break;
                 case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: {
                     final HearingAidDeviceConnectionInfo info =
@@ -869,8 +885,10 @@
                                     + " addr=" + info.mDevice.getAddress()
                                     + " supprNoisy=" + info.mSupprNoisy
                                     + " src=" + info.mEventSource)).printLog(TAG));
-                    mDeviceInventory.setBluetoothHearingAidDeviceConnectionState(
-                            info.mDevice, info.mState, info.mSupprNoisy, info.mMusicDevice);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.setBluetoothHearingAidDeviceConnectionState(
+                                info.mDevice, info.mState, info.mSupprNoisy, info.mMusicDevice);
+                    }
                 } break;
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
@@ -955,57 +973,46 @@
     /** If the msg is already queued, queue this one and leave the old. */
     private static final int SENDMSG_QUEUE = 2;
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendMsg(int msg, int existingMsgPolicy, int delay) {
         sendIILMsg(msg, existingMsgPolicy, 0, 0, null, delay);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendILMsg(int msg, int existingMsgPolicy, int arg, Object obj, int delay) {
         sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, delay);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendLMsg(int msg, int existingMsgPolicy, Object obj, int delay) {
         sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, delay);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendIMsg(int msg, int existingMsgPolicy, int arg, int delay) {
         sendIILMsg(msg, existingMsgPolicy, arg, 0, null, delay);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendMsgNoDelay(int msg, int existingMsgPolicy) {
         sendIILMsg(msg, existingMsgPolicy, 0, 0, null, 0);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendIMsgNoDelay(int msg, int existingMsgPolicy, int arg) {
         sendIILMsg(msg, existingMsgPolicy, arg, 0, null, 0);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendIIMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2) {
         sendIILMsg(msg, existingMsgPolicy, arg1, arg2, null, 0);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendILMsgNoDelay(int msg, int existingMsgPolicy, int arg, Object obj) {
         sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, 0);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendLMsgNoDelay(int msg, int existingMsgPolicy, Object obj) {
         sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, 0);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendIILMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj) {
         sendIILMsg(msg, existingMsgPolicy, arg1, arg2, obj, 0);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendIILMsg(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj,
                             int delay) {
         if (existingMsgPolicy == SENDMSG_REPLACE) {
@@ -1024,29 +1031,31 @@
             Binder.restoreCallingIdentity(identity);
         }
 
-        long time = SystemClock.uptimeMillis() + delay;
+        synchronized (sLastDeviceConnectionMsgTimeLock) {
+            long time = SystemClock.uptimeMillis() + delay;
 
-        switch (msg) {
-            case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
-            case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
-            case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
-            case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
-            case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
-            case MSG_IL_BTA2DP_DOCK_TIMEOUT:
-            case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
-            case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
-                if (sLastDeviceConnectMsgTime >= time) {
-                    // add a little delay to make sure messages are ordered as expected
-                    time = sLastDeviceConnectMsgTime + 30;
-                }
-                sLastDeviceConnectMsgTime = time;
-                break;
-            default:
-                break;
+            switch (msg) {
+                case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+                case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
+                case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
+                case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+                case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+                case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+                case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+                case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+                    if (sLastDeviceConnectMsgTime >= time) {
+                        // add a little delay to make sure messages are ordered as expected
+                        time = sLastDeviceConnectMsgTime + 30;
+                    }
+                    sLastDeviceConnectMsgTime = time;
+                    break;
+                default:
+                    break;
+            }
+
+            mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj),
+                    time);
         }
-
-        mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj),
-                time);
     }
 
     //-------------------------------------------------------------
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 075842b..77b3fee 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -469,11 +469,12 @@
 
     // List of binder death handlers for setMode() client processes.
     // The last process to have called setMode() is at the top of the list.
-    private final ArrayList<SetModeDeathHandler> mSetModeDeathHandlers =
+    // package-private so it can be accessed in AudioDeviceBroker.getSetModeDeathHandlers
+    //TODO candidate to be moved to separate class that handles synchronization
+    @GuardedBy("mDeviceBroker.mSetModeLock")
+    /*package*/ final ArrayList<SetModeDeathHandler> mSetModeDeathHandlers =
             new ArrayList<SetModeDeathHandler>();
 
-    private volatile int mCurrentModeOwnerPid = 0;
-
     // true if boot sequence has been completed
     private boolean mSystemReady;
     // true if Intent.ACTION_USER_SWITCHED has ever been received
@@ -3149,10 +3150,15 @@
      * @return 0 if nobody owns the mode
      */
     /*package*/ int getModeOwnerPid() {
-        return  mCurrentModeOwnerPid;
+        int modeOwnerPid = 0;
+        try {
+            modeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
+        } catch (Exception e) {
+            // nothing to do, modeOwnerPid is not modified
+        }
+        return modeOwnerPid;
     }
 
-
     private class SetModeDeathHandler implements IBinder.DeathRecipient {
         private IBinder mCb; // To be notified of client's death
         private int mPid;
@@ -3166,7 +3172,7 @@
         public void binderDied() {
             int oldModeOwnerPid = 0;
             int newModeOwnerPid = 0;
-            synchronized (mSetModeDeathHandlers) {
+            synchronized (mDeviceBroker.mSetModeLock) {
                 Log.w(TAG, "setMode() client died");
                 if (!mSetModeDeathHandlers.isEmpty()) {
                     oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
@@ -3177,15 +3183,11 @@
                 } else {
                     newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, TAG);
                 }
-
-                if (newModeOwnerPid != oldModeOwnerPid) {
-                    mCurrentModeOwnerPid = newModeOwnerPid;
-                    // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
-                    // connections not started by the application changing the mode when pid changes
-                    if (newModeOwnerPid != 0) {
-                        mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
-                    }
-                }
+            }
+            // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
+            // SCO connections not started by the application changing the mode when pid changes
+            if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
+                mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
             }
         }
 
@@ -3208,17 +3210,15 @@
 
     /** @see AudioManager#setMode(int) */
     public void setMode(int mode, IBinder cb, String callingPackage) {
-        if (DEBUG_MODE) {
-            Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")");
-        }
+        if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); }
         if (!checkAudioSettingsPermission("setMode()")) {
             return;
         }
 
-        if ((mode == AudioSystem.MODE_IN_CALL)
-                && (mContext.checkCallingOrSelfPermission(
+        if ( (mode == AudioSystem.MODE_IN_CALL) &&
+                (mContext.checkCallingOrSelfPermission(
                         android.Manifest.permission.MODIFY_PHONE_STATE)
-                        != PackageManager.PERMISSION_GRANTED)) {
+                            != PackageManager.PERMISSION_GRANTED)) {
             Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode(MODE_IN_CALL) from pid="
                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
             return;
@@ -3230,7 +3230,7 @@
 
         int oldModeOwnerPid = 0;
         int newModeOwnerPid = 0;
-        synchronized (mSetModeDeathHandlers) {
+        synchronized (mDeviceBroker.mSetModeLock) {
             if (!mSetModeDeathHandlers.isEmpty()) {
                 oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
             }
@@ -3238,21 +3238,17 @@
                 mode = mMode;
             }
             newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage);
-
-            if (newModeOwnerPid != oldModeOwnerPid) {
-                mCurrentModeOwnerPid = newModeOwnerPid;
-                // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
-                // SCO connections not started by the application changing the mode when pid changes
-                if (newModeOwnerPid != 0) {
-                    mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
-                }
-            }
+        }
+        // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
+        // SCO connections not started by the application changing the mode when pid changes
+        if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
+            mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
         }
     }
 
     // setModeInt() returns a valid PID if the audio mode was successfully set to
     // any mode other than NORMAL.
-    @GuardedBy("mSetModeDeathHandlers")
+    @GuardedBy("mDeviceBroker.mSetModeLock")
     private int setModeInt(int mode, IBinder cb, int pid, String caller) {
         if (DEBUG_MODE) { Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid + ", caller="
                 + caller + ")"); }
@@ -3591,7 +3587,9 @@
                 !mSystemReady) {
             return;
         }
-        mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource);
+        synchronized (mDeviceBroker.mSetModeLock) {
+            mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource);
+        }
     }
 
     /** @see AudioManager#stopBluetoothSco() */
@@ -3603,7 +3601,9 @@
         final String eventSource =  new StringBuilder("stopBluetoothSco()")
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
                 .append(Binder.getCallingPid()).toString();
-        mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource);
+        synchronized (mDeviceBroker.mSetModeLock) {
+            mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource);
+        }
     }
 
 
@@ -4352,7 +4352,7 @@
 
     // NOTE: Locking order for synchronized objects related to volume or ringer mode management:
     //  1 mScoclient OR mSafeMediaVolumeState
-    //  2   mSetModeDeathHandlers
+    //  2   mSetModeLock
     //  3     mSettingsLock
     //  4       VolumeStreamState.class
     private class VolumeStreamState {
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 625b6b6..9f1a6bd 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -171,6 +171,8 @@
     //----------------------------------------------------------------------
     // Interface for AudioDeviceBroker
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void onSystemReady() {
         mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR;
         resetBluetoothSco();
@@ -243,6 +245,8 @@
         return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void receiveBtEvent(Intent intent) {
         final String action = intent.getAction();
         if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
@@ -329,6 +333,8 @@
      *
      * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
      */
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void disconnectBluetoothSco(int exceptPid) {
         checkScoAudioState();
         if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
@@ -337,6 +343,8 @@
         clearAllScoClients(exceptPid, true);
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void startBluetoothScoForClient(IBinder cb, int scoAudioMode,
                 @NonNull String eventSource) {
         ScoClient client = getScoClient(cb, true);
@@ -356,6 +364,8 @@
         Binder.restoreCallingIdentity(ident);
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void stopBluetoothScoForClient(IBinder cb,
             @NonNull String eventSource) {
         ScoClient client = getScoClient(cb, false);
@@ -413,6 +423,8 @@
         mDeviceBroker.postDisconnectHearingAid();
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void resetBluetoothSco() {
         clearAllScoClients(0, false);
         mScoAudioState = SCO_STATE_INACTIVE;
@@ -421,6 +433,8 @@
         mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void disconnectHeadset() {
         setBtScoActiveDevice(null);
         mBluetoothHeadset = null;
@@ -466,6 +480,8 @@
                 /*eventSource*/ "mBluetoothProfileServiceListener");
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void onHeadsetProfileConnected(BluetoothHeadset headset) {
         // Discard timeout message
         mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
@@ -552,6 +568,8 @@
         return result;
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     @GuardedBy("BtHelper.this")
     private void setBtScoActiveDevice(BluetoothDevice btDevice) {
         Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
@@ -634,6 +652,8 @@
             };
 
     //----------------------------------------------------------------------
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void scoClientDied(Object obj) {
         final ScoClient client = (ScoClient) obj;
         Log.w(TAG, "SCO client died");
@@ -664,6 +684,8 @@
             mDeviceBroker.postScoClientDied(this);
         }
 
+        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+        // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
         @GuardedBy("BtHelper.this")
         void incCount(int scoAudioMode) {
             if (!requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode)) {
@@ -683,6 +705,8 @@
             mStartcount++;
         }
 
+        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+        // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
         @GuardedBy("BtHelper.this")
         void decCount() {
             if (mStartcount == 0) {
@@ -702,6 +726,8 @@
             }
         }
 
+        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+        // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
         @GuardedBy("BtHelper.this")
         void clearCount(boolean stopSco) {
             if (mStartcount != 0) {
@@ -738,6 +764,8 @@
             return count;
         }
 
+        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+        //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
         @GuardedBy("BtHelper.this")
         private boolean requestScoState(int state, int scoAudioMode) {
             checkScoAudioState();
@@ -931,6 +959,8 @@
         return null;
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     @GuardedBy("BtHelper.this")
     private void clearAllScoClients(int exceptPid, boolean stopSco) {
         ScoClient savedClient = null;
diff --git a/services/core/java/com/android/server/integrity/OWNERS b/services/core/java/com/android/server/integrity/OWNERS
new file mode 100644
index 0000000..019aa4f
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/OWNERS
@@ -0,0 +1,6 @@
+omernebil@google.com
+khelmy@google.com
+mdchurchill@google.com
+sturla@google.com
+songpan@google.com
+bjy@google.com
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 587862a..2a85c89 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -37,7 +37,7 @@
 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_REVOKE_ON_UPGRADE;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
 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;
@@ -581,16 +581,6 @@
 
     private static final String PACKAGE_SCHEME = "package";
 
-    private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
-
-    private static final String PRODUCT_OVERLAY_DIR = "/product/overlay";
-
-    private static final String SYSTEM_EXT_OVERLAY_DIR = "/system_ext/overlay";
-
-    private static final String ODM_OVERLAY_DIR = "/odm/overlay";
-
-    private static final String OEM_OVERLAY_DIR = "/oem/overlay";
-
     /** Canonical intent used to identify what counts as a "web browser" app */
     private static final Intent sBrowserIntent;
     static {
@@ -756,6 +746,26 @@
     private final Injector mInjector;
 
     /**
+     * The list of all system partitions that may contain packages in ascending order of
+     * specificity (the more generic, the earlier in the list a partition appears).
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final List<SystemPartition> SYSTEM_PARTITIONS = Collections.unmodifiableList(
+            Arrays.asList(
+                    new SystemPartition(Environment.getRootDirectory(), 0 /* scanFlag */,
+                            true /* hasPriv */, false /* hasOverlays */),
+                    new SystemPartition(Environment.getVendorDirectory(), SCAN_AS_VENDOR,
+                            true /* hasPriv */, true /* hasOverlays */),
+                    new SystemPartition(Environment.getOdmDirectory(), SCAN_AS_ODM,
+                            true /* hasPriv */, true /* hasOverlays */),
+                    new SystemPartition(Environment.getOemDirectory(), SCAN_AS_OEM,
+                            false /* hasPriv */, true /* hasOverlays */),
+                    new SystemPartition(Environment.getProductDirectory(), SCAN_AS_PRODUCT,
+                            true /* hasPriv */, true /* hasOverlays */),
+                    new SystemPartition(Environment.getSystemExtDirectory(), SCAN_AS_SYSTEM_EXT,
+                            true /* hasPriv */, true /* hasOverlays */)));
+
+    /**
      * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
      *
      * NOTE: All getters should return the same instance for every call.
@@ -1582,7 +1592,7 @@
     private static final int USER_RUNTIME_GRANT_MASK =
             FLAG_PERMISSION_USER_SET
             | FLAG_PERMISSION_USER_FIXED
-            | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+            | FLAG_PERMISSION_REVOKED_COMPAT;
 
     final @Nullable String mRequiredVerifierPackage;
     final @NonNull String mRequiredInstallerPackage;
@@ -2552,6 +2562,51 @@
         }
     }
 
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static class SystemPartition {
+        public final File folder;
+        public final int scanFlag;
+        public final File appFolder;
+        @Nullable
+        public final File privAppFolder;
+        @Nullable
+        public final File overlayFolder;
+
+        private SystemPartition(File folder, int scanFlag, boolean hasPrivApps,
+                boolean hasOverlays) {
+            this.folder = folder;
+            this.scanFlag = scanFlag;
+            this.appFolder = toCanonical(new File(folder, "app"));
+            this.privAppFolder = hasPrivApps ? toCanonical(new File(folder, "priv-app")) : null;
+            this.overlayFolder = hasOverlays ? toCanonical(new File(folder, "overlay")) : null;
+        }
+
+        public boolean containsPrivApp(File scanFile) {
+            return FileUtils.contains(privAppFolder, scanFile);
+        }
+
+        public boolean containsApp(File scanFile) {
+            return FileUtils.contains(appFolder, scanFile);
+        }
+
+        public boolean containsPath(String path) {
+            return path.startsWith(folder.getPath() + "/");
+        }
+
+        public boolean containsPrivPath(String path) {
+            return privAppFolder != null && path.startsWith(privAppFolder.getPath() + "/");
+        }
+
+        private static File toCanonical(File dir) {
+            try {
+                return dir.getCanonicalFile();
+            } catch (IOException e) {
+                // failed to look up canonical path, continue with original one
+                return dir;
+            }
+        }
+    }
+
     public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
                 Trace.TRACE_TAG_PACKAGE_MANAGER);
@@ -2775,215 +2830,35 @@
             // any apps.)
             // For security and version matching reason, only consider overlay packages if they
             // reside in the right directory.
-            scanDirTracedLI(new File(VENDOR_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR,
-                    0);
-            scanDirTracedLI(new File(PRODUCT_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRODUCT,
-                    0);
-            scanDirTracedLI(new File(SYSTEM_EXT_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_SYSTEM_EXT,
-                    0);
-            scanDirTracedLI(new File(ODM_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_ODM,
-                    0);
-            scanDirTracedLI(new File(OEM_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_OEM,
-                    0);
+            final int systemParseFlags = mDefParseFlags | PackageParser.PARSE_IS_SYSTEM_DIR;
+            final int systemScanFlags = scanFlags | SCAN_AS_SYSTEM;
+            for (int i = SYSTEM_PARTITIONS.size() - 1; i >= 0; i--) {
+                final SystemPartition partition = SYSTEM_PARTITIONS.get(i);
+                if (partition.overlayFolder == null) {
+                    continue;
+                }
+                scanDirTracedLI(partition.overlayFolder, systemParseFlags,
+                        systemScanFlags | partition.scanFlag, 0);
+            }
 
             mParallelPackageParserCallback.findStaticOverlayPackages();
 
-            // Find base frameworks (resource packages without code).
-            scanDirTracedLI(frameworkDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_NO_DEX
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRIVILEGED,
-                    0);
+            scanDirTracedLI(frameworkDir, systemParseFlags,
+                    systemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0);
             if (!mPackages.containsKey("android")) {
                 throw new IllegalStateException(
                         "Failed to load frameworks package; check log for warnings");
             }
-
-            // Collect privileged system packages.
-            final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
-            scanDirTracedLI(privilegedAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRIVILEGED,
-                    0);
-
-            // Collect ordinary system packages.
-            final File systemAppDir = new File(Environment.getRootDirectory(), "app");
-            scanDirTracedLI(systemAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM,
-                    0);
-
-            // Collect privileged vendor packages.
-            File privilegedVendorAppDir = new File(Environment.getVendorDirectory(), "priv-app");
-            try {
-                privilegedVendorAppDir = privilegedVendorAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
+            for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
+                final SystemPartition partition = SYSTEM_PARTITIONS.get(i);
+                if (partition.privAppFolder != null) {
+                    scanDirTracedLI(partition.privAppFolder, systemParseFlags,
+                            systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0);
+                }
+                scanDirTracedLI(partition.appFolder, systemParseFlags,
+                        systemScanFlags | partition.scanFlag, 0);
             }
-            scanDirTracedLI(privilegedVendorAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR
-                    | SCAN_AS_PRIVILEGED,
-                    0);
 
-            // Collect ordinary vendor packages.
-            File vendorAppDir = new File(Environment.getVendorDirectory(), "app");
-            try {
-                vendorAppDir = vendorAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(vendorAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR,
-                    0);
-
-            // Collect privileged odm packages. /odm is another vendor partition
-            // other than /vendor.
-            File privilegedOdmAppDir = new File(Environment.getOdmDirectory(),
-                        "priv-app");
-            try {
-                privilegedOdmAppDir = privilegedOdmAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(privilegedOdmAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR
-                    | SCAN_AS_PRIVILEGED,
-                    0);
-
-            // Collect ordinary odm packages. /odm is another vendor partition
-            // other than /vendor.
-            File odmAppDir = new File(Environment.getOdmDirectory(), "app");
-            try {
-                odmAppDir = odmAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(odmAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR,
-                    0);
-
-            // Collect all OEM packages.
-            final File oemAppDir = new File(Environment.getOemDirectory(), "app");
-            scanDirTracedLI(oemAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_OEM,
-                    0);
-
-            // Collected privileged /product packages.
-            File privilegedProductAppDir = new File(Environment.getProductDirectory(), "priv-app");
-            try {
-                privilegedProductAppDir = privilegedProductAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(privilegedProductAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRODUCT
-                    | SCAN_AS_PRIVILEGED,
-                    0);
-
-            // Collect ordinary /product packages.
-            File productAppDir = new File(Environment.getProductDirectory(), "app");
-            try {
-                productAppDir = productAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(productAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRODUCT,
-                    0);
-
-            // Collected privileged /system_ext packages.
-            File privilegedSystemExtAppDir =
-                    new File(Environment.getSystemExtDirectory(), "priv-app");
-            try {
-                privilegedSystemExtAppDir =
-                        privilegedSystemExtAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(privilegedSystemExtAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_SYSTEM_EXT
-                    | SCAN_AS_PRIVILEGED,
-                    0);
-
-            // Collect ordinary /system_ext packages.
-            File systemExtAppDir = new File(Environment.getSystemExtDirectory(), "app");
-            try {
-                systemExtAppDir = systemExtAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(systemExtAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_SYSTEM_EXT,
-                    0);
 
             // Prune any system packages that no longer exist.
             final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
@@ -3151,89 +3026,26 @@
                         logCriticalInfo(Log.WARN, "Expected better " + packageName
                                 + " but never showed up; reverting to system");
 
-                        final @ParseFlags int reparseFlags;
-                        final @ScanFlags int rescanFlags;
-                        if (FileUtils.contains(privilegedAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_PRIVILEGED;
-                        } else if (FileUtils.contains(systemAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM;
-                        } else if (FileUtils.contains(privilegedVendorAppDir, scanFile)
-                                || FileUtils.contains(privilegedOdmAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_VENDOR
-                                    | SCAN_AS_PRIVILEGED;
-                        } else if (FileUtils.contains(vendorAppDir, scanFile)
-                                || FileUtils.contains(odmAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_VENDOR;
-                        } else if (FileUtils.contains(oemAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_OEM;
-                        } else if (FileUtils.contains(privilegedProductAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_PRODUCT
-                                    | SCAN_AS_PRIVILEGED;
-                        } else if (FileUtils.contains(productAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_PRODUCT;
-                        } else if (FileUtils.contains(privilegedSystemExtAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_SYSTEM_EXT
-                                    | SCAN_AS_PRIVILEGED;
-                        } else if (FileUtils.contains(systemExtAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_SYSTEM_EXT;
-                        } else {
+                        @ParseFlags int reparseFlags = 0;
+                        @ScanFlags int rescanFlags = 0;
+                        for (int i1 = 0, size = SYSTEM_PARTITIONS.size(); i1 < size; i1++) {
+                            SystemPartition partition = SYSTEM_PARTITIONS.get(i1);
+                            if (partition.containsPrivApp(scanFile)) {
+                                reparseFlags = systemParseFlags;
+                                rescanFlags = systemScanFlags | SCAN_AS_PRIVILEGED
+                                        | partition.scanFlag;
+                                break;
+                            }
+                            if (partition.containsApp(scanFile)) {
+                                reparseFlags = systemParseFlags;
+                                rescanFlags = systemScanFlags | partition.scanFlag;
+                                break;
+                            }
+                        }
+                        if (rescanFlags == 0) {
                             Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile);
                             continue;
                         }
-
                         mSettings.enableSystemPackageLPw(packageName);
 
                         try {
@@ -18079,70 +17891,15 @@
     }
 
     static boolean locationIsPrivileged(String path) {
-        try {
-            final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
-            final File privilegedVendorAppDir = new File(Environment.getVendorDirectory(), "priv-app");
-            final File privilegedOdmAppDir = new File(Environment.getOdmDirectory(), "priv-app");
-            final File privilegedProductAppDir = new File(Environment.getProductDirectory(), "priv-app");
-            final File privilegedSystemExtAppDir =
-                    new File(Environment.getSystemExtDirectory(), "priv-app");
-            return path.startsWith(privilegedAppDir.getCanonicalPath() + "/")
-                    || path.startsWith(privilegedVendorAppDir.getCanonicalPath() + "/")
-                    || path.startsWith(privilegedOdmAppDir.getCanonicalPath() + "/")
-                    || path.startsWith(privilegedProductAppDir.getCanonicalPath() + "/")
-                    || path.startsWith(privilegedSystemExtAppDir.getCanonicalPath() + "/");
-        } catch (IOException e) {
-            Slog.e(TAG, "Unable to access code path " + path);
+        for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
+            SystemPartition partition = SYSTEM_PARTITIONS.get(i);
+            if (partition.containsPrivPath(path)) {
+                return true;
+            }
         }
         return false;
     }
 
-    static boolean locationIsOem(String path) {
-        try {
-            return path.startsWith(Environment.getOemDirectory().getCanonicalPath() + "/");
-        } catch (IOException e) {
-            Slog.e(TAG, "Unable to access code path " + path);
-        }
-        return false;
-    }
-
-    static boolean locationIsVendor(String path) {
-        try {
-            return path.startsWith(Environment.getVendorDirectory().getCanonicalPath() + "/")
-                    || path.startsWith(Environment.getOdmDirectory().getCanonicalPath() + "/");
-        } catch (IOException e) {
-            Slog.e(TAG, "Unable to access code path " + path);
-        }
-        return false;
-    }
-
-    static boolean locationIsProduct(String path) {
-        try {
-            return path.startsWith(Environment.getProductDirectory().getCanonicalPath() + "/");
-        } catch (IOException e) {
-            Slog.e(TAG, "Unable to access code path " + path);
-        }
-        return false;
-    }
-
-    static boolean locationIsSystemExt(String path) {
-        try {
-            return path.startsWith(
-              Environment.getSystemExtDirectory().getCanonicalPath() + "/");
-        } catch (IOException e) {
-            Slog.e(TAG, "Unable to access code path " + path);
-        }
-        return false;
-    }
-
-    static boolean locationIsOdm(String path) {
-        try {
-            return path.startsWith(Environment.getOdmDirectory().getCanonicalPath() + "/");
-        } catch (IOException e) {
-            Slog.e(TAG, "Unable to access code path " + path);
-        }
-        return false;
-    }
 
     /*
      * Tries to delete system package.
@@ -18253,23 +18010,15 @@
                 | PackageParser.PARSE_MUST_BE_APK
                 | PackageParser.PARSE_IS_SYSTEM_DIR;
         @ScanFlags int scanFlags = SCAN_AS_SYSTEM;
-        if (locationIsPrivileged(codePathString)) {
-            scanFlags |= SCAN_AS_PRIVILEGED;
-        }
-        if (locationIsOem(codePathString)) {
-            scanFlags |= SCAN_AS_OEM;
-        }
-        if (locationIsVendor(codePathString)) {
-            scanFlags |= SCAN_AS_VENDOR;
-        }
-        if (locationIsProduct(codePathString)) {
-            scanFlags |= SCAN_AS_PRODUCT;
-        }
-        if (locationIsSystemExt(codePathString)) {
-            scanFlags |= SCAN_AS_SYSTEM_EXT;
-        }
-        if (locationIsOdm(codePathString)) {
-            scanFlags |= SCAN_AS_ODM;
+        for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
+            SystemPartition partition = SYSTEM_PARTITIONS.get(i);
+            if (partition.containsPath(codePathString)) {
+                scanFlags |= partition.scanFlag;
+                if (partition.containsPrivPath(codePathString)) {
+                    scanFlags |= SCAN_AS_PRIVILEGED;
+                }
+                break;
+            }
         }
 
         final File codePath = new File(codePathString);
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 1cea4ca..6b4ef69 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -318,7 +318,7 @@
         // The APEX part of the session is activated, proceed with the installation of APKs.
         try {
             Slog.d(TAG, "Installing APK packages in session " + session.sessionId);
-            installApksInSession(session, /* preReboot */ false);
+            installApksInSession(session);
         } catch (PackageManagerException e) {
             session.setStagedSessionFailed(e.error, e.getMessage());
 
@@ -410,72 +410,23 @@
         return apkSession;
     }
 
-    private void commitApkSession(@NonNull PackageInstallerSession apkSession,
-            PackageInstallerSession originalSession, boolean preReboot)
-            throws PackageManagerException {
-        final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
-                : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
-        if (preReboot) {
-            final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync(
-                    (Intent result) -> {
-                        int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                                PackageInstaller.STATUS_FAILURE);
-                        if (status != PackageInstaller.STATUS_SUCCESS) {
-                            final String errorMessage = result.getStringExtra(
-                                    PackageInstaller.EXTRA_STATUS_MESSAGE);
-                            Slog.e(TAG, "Failure to install APK staged session "
-                                    + originalSession.sessionId + " [" + errorMessage + "]");
-                            originalSession.setStagedSessionFailed(errorCode, errorMessage);
-                            return;
-                        }
-                        mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
-                                originalSession.sessionId);
-                    });
-            apkSession.commit(receiver.getIntentSender(), false);
-            return;
-        }
-
-        if ((apkSession.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
-            // If rollback is available for this session, notify the rollback
-            // manager of the apk session so it can properly enable rollback.
-            final IRollbackManager rm = IRollbackManager.Stub.asInterface(
-                    ServiceManager.getService(Context.ROLLBACK_SERVICE));
-            try {
-                rm.notifyStagedApkSession(originalSession.sessionId, apkSession.sessionId);
-            } catch (RemoteException re) {
-                Slog.e(TAG, "Failed to notifyStagedApkSession for session: "
-                        + originalSession.sessionId, re);
-            }
-        }
-
-        final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync();
-        apkSession.commit(receiver.getIntentSender(), false);
-        final Intent result = receiver.getResult();
-        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        if (status != PackageInstaller.STATUS_SUCCESS) {
-            final String errorMessage = result.getStringExtra(
-                    PackageInstaller.EXTRA_STATUS_MESSAGE);
-            Slog.e(TAG, "Failure to install APK staged session "
-                    + originalSession.sessionId + " [" + errorMessage + "]");
-            throw new PackageManagerException(errorCode, errorMessage);
-        }
-    }
-
-    private void installApksInSession(@NonNull PackageInstallerSession session,
-                                         boolean preReboot) throws PackageManagerException {
+    /**
+     * Extract apks in the given session into a new session. Returns {@code null} if there is no
+     * apks in the given session. Only parent session is returned for multi-package session.
+     */
+    @Nullable
+    private PackageInstallerSession extractApksInSession(PackageInstallerSession session,
+            boolean preReboot) throws PackageManagerException {
         final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
                 : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
         if (!session.isMultiPackage() && !isApexSession(session)) {
-            // APK single-packaged staged session. Do a regular install.
-            PackageInstallerSession apkSession = createAndWriteApkSession(session, preReboot);
-            commitApkSession(apkSession, session, preReboot);
+            return createAndWriteApkSession(session, preReboot);
         } else if (session.isMultiPackage()) {
             // For multi-package staged sessions containing APKs, we identify which child sessions
             // contain an APK, and with those then create a new multi-package group of sessions,
             // carrying over all the session parameters and unmarking them as staged. On commit the
             // sessions will be installed atomically.
-            List<PackageInstallerSession> childSessions;
+            final List<PackageInstallerSession> childSessions;
             synchronized (mStagedSessions) {
                 childSessions =
                         Arrays.stream(session.getChildSessionIds())
@@ -487,18 +438,18 @@
             }
             if (childSessions.isEmpty()) {
                 // APEX-only multi-package staged session, nothing to do.
-                return;
+                return null;
             }
-            PackageInstaller.SessionParams params = session.params.copy();
+            final PackageInstaller.SessionParams params = session.params.copy();
             params.isStaged = false;
             if (preReboot) {
                 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
             }
             // TODO(b/129744602): use the userid from the original session.
-            int apkParentSessionId = mPi.createSession(
+            final int apkParentSessionId = mPi.createSession(
                     params, session.getInstallerPackageName(),
                     0 /* UserHandle.SYSTEM */);
-            PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
+            final PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
             try {
                 apkParentSession.open();
             } catch (IOException e) {
@@ -519,9 +470,75 @@
                             "Failed to add a child session " + apkChildSession.sessionId);
                 }
             }
-            commitApkSession(apkParentSession, session, preReboot);
+            return apkParentSession;
         }
-        // APEX single-package staged session, nothing to do.
+        return null;
+    }
+
+    private void verifyApksInSession(PackageInstallerSession session)
+            throws PackageManagerException {
+
+        final PackageInstallerSession apksToVerify = extractApksInSession(
+                session,  /* preReboot */ true);
+        if (apksToVerify == null) {
+            return;
+        }
+
+        final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync(
+                (Intent result) -> {
+                    int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                            PackageInstaller.STATUS_FAILURE);
+                    if (status != PackageInstaller.STATUS_SUCCESS) {
+                        final String errorMessage = result.getStringExtra(
+                                PackageInstaller.EXTRA_STATUS_MESSAGE);
+                        Slog.e(TAG, "Failure to verify APK staged session "
+                                + session.sessionId + " [" + errorMessage + "]");
+                        session.setStagedSessionFailed(
+                                SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage);
+                        return;
+                    }
+                    mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
+                            session.sessionId);
+                });
+
+        apksToVerify.commit(receiver.getIntentSender(), false);
+    }
+
+    private void installApksInSession(@NonNull PackageInstallerSession session)
+            throws PackageManagerException {
+
+        final PackageInstallerSession apksToInstall = extractApksInSession(
+                session, /* preReboot */ false);
+        if (apksToInstall == null) {
+            return;
+        }
+
+        if ((apksToInstall.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
+            // If rollback is available for this session, notify the rollback
+            // manager of the apk session so it can properly enable rollback.
+            final IRollbackManager rm = IRollbackManager.Stub.asInterface(
+                    ServiceManager.getService(Context.ROLLBACK_SERVICE));
+            try {
+                rm.notifyStagedApkSession(session.sessionId, apksToInstall.sessionId);
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Failed to notifyStagedApkSession for session: "
+                        + session.sessionId, re);
+            }
+        }
+
+        final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync();
+        apksToInstall.commit(receiver.getIntentSender(), false);
+        final Intent result = receiver.getResult();
+        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                PackageInstaller.STATUS_FAILURE);
+        if (status != PackageInstaller.STATUS_SUCCESS) {
+            final String errorMessage = result.getStringExtra(
+                    PackageInstaller.EXTRA_STATUS_MESSAGE);
+            Slog.e(TAG, "Failure to install APK staged session "
+                    + session.sessionId + " [" + errorMessage + "]");
+            throw new PackageManagerException(
+                    SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage);
+        }
     }
 
     void commitSession(@NonNull PackageInstallerSession session) {
@@ -836,8 +853,8 @@
                 Slog.d(TAG, "Running a pre-reboot verification for APKs in session "
                         + session.sessionId + " by performing a dry-run install");
 
-                // installApksInSession will notify the handler when APK verification is complete
-                installApksInSession(session, /* preReboot */ true);
+                // verifyApksInSession will notify the handler when APK verification is complete
+                verifyApksInSession(session);
                 // TODO(b/118865310): abort the session on apexd.
             } catch (PackageManagerException e) {
                 session.setStagedSessionFailed(e.error, e.getMessage());
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 3e655ed..793cdd2 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -190,6 +190,7 @@
     static {
         STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
         STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+        STORAGE_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
     }
 
     private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1;
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 a57321e..b831374 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -24,7 +24,7 @@
 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_REVOKED_COMPAT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
@@ -1514,7 +1514,7 @@
         // 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_REVOKED_COMPAT
                 | FLAG_PERMISSION_REVIEW_REQUIRED;
 
         final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED
@@ -1624,7 +1624,7 @@
             final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
             final int targetSdk = mPackageManagerInt.getUidTargetSdkVersion(uid);
             final int flags = (targetSdk < Build.VERSION_CODES.M && bp.isRuntime())
-                    ? FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKE_ON_UPGRADE
+                    ? FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKED_COMPAT
                     : 0;
 
             updatePermissionFlagsInternal(
@@ -2536,8 +2536,8 @@
                                         wasChanged = true;
                                     }
 
-                                    if ((flags & FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0) {
-                                        flags &= ~FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+                                    if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0) {
+                                        flags &= ~FLAG_PERMISSION_REVOKED_COMPAT;
                                         wasChanged = true;
                                     // Hard restricted permissions cannot be held.
                                     } else if (!permissionPolicyInitialized
@@ -2556,7 +2556,7 @@
                                                 bp.getSourcePackageName())) {
                                             if (!bp.isRemoved()) {
                                                 flags |= FLAG_PERMISSION_REVIEW_REQUIRED
-                                                        | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+                                                        | FLAG_PERMISSION_REVOKED_COMPAT;
                                                 wasChanged = true;
                                             }
                                         }
@@ -2671,8 +2671,8 @@
                                         wasChanged = true;
                                     }
 
-                                    if ((flags & FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0) {
-                                        flags &= ~FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+                                    if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0) {
+                                        flags &= ~FLAG_PERMISSION_REVOKED_COMPAT;
                                         wasChanged = true;
                                     // Hard restricted permissions cannot be held.
                                     } else if (!permissionPolicyInitialized ||
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
index 9cb2441..bbee393b 100644
--- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -370,8 +370,7 @@
                         // Take an "interactive" bugreport.
                         MetricsLogger.action(mContext,
                                 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
-                        ActivityManager.getService().requestBugReport(
-                                ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
+                        ActivityManager.getService().requestInteractiveBugReport();
                     } catch (RemoteException e) {
                     }
                 }
@@ -388,8 +387,7 @@
             try {
                 // Take a "full" bugreport.
                 MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
-                ActivityManager.getService().requestBugReport(
-                        ActivityManager.BUGREPORT_OPTION_FULL);
+                ActivityManager.getService().requestFullBugReport();
             } catch (RemoteException e) {
             }
             return false;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ab531899..88b1793 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -204,6 +204,7 @@
 import com.android.internal.os.RoSystemProperties;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IShortcutService;
+import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.internal.policy.PhoneWindow;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.ArrayUtils;
@@ -1603,7 +1604,7 @@
             mDisplayId = displayId;
         }
 
-        int handleHomeButton(WindowState win, KeyEvent event) {
+        int handleHomeButton(IBinder focusedToken, KeyEvent event) {
             final boolean keyguardOn = keyguardOn();
             final int repeatCount = event.getRepeatCount();
             final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
@@ -1646,18 +1647,18 @@
                 return -1;
             }
 
-            // If a system window has focus, then it doesn't make sense
-            // right now to interact with applications.
-            WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
-            if (attrs != null) {
-                final int type = attrs.type;
-                if (type == TYPE_KEYGUARD_DIALOG
-                        || (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+            final KeyInterceptionInfo info =
+                    mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
+            if (info != null) {
+                // If a system window has focus, then it doesn't make sense
+                // right now to interact with applications.
+                if (info.layoutParamsType == TYPE_KEYGUARD_DIALOG
+                        || (info.layoutParamsPrivateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
                     // the "app" is keyguard, so give it the key
                     return 0;
                 }
                 for (int t : WINDOW_TYPES_WHERE_HOME_DOESNT_WORK) {
-                    if (type == t) {
+                    if (info.layoutParamsType == t) {
                         // don't do anything, but also don't pass it to the app
                         return -1;
                     }
@@ -2598,8 +2599,9 @@
     // TODO(b/117479243): handle it in InputPolicy
     /** {@inheritDoc} */
     @Override
-    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
-        final long result = interceptKeyBeforeDispatchingInner(win, event, policyFlags);
+    public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
+            int policyFlags) {
+        final long result = interceptKeyBeforeDispatchingInner(focusedToken, event, policyFlags);
         final int eventDisplayId = event.getDisplayId();
         if (result == 0 && !mPerDisplayFocusEnabled
                 && eventDisplayId != INVALID_DISPLAY && eventDisplayId != mTopFocusedDisplayId) {
@@ -2627,7 +2629,7 @@
         return result;
     }
 
-    private long interceptKeyBeforeDispatchingInner(WindowState win, KeyEvent event,
+    private long interceptKeyBeforeDispatchingInner(IBinder focusedToken, KeyEvent event,
             int policyFlags) {
         final boolean keyguardOn = keyguardOn();
         final int keyCode = event.getKeyCode();
@@ -2730,7 +2732,7 @@
                 handler = new DisplayHomeButtonHandler(displayId);
                 mDisplayHomeButtonHandlers.put(displayId, handler);
             }
-            return handler.handleHomeButton(win, event);
+            return handler.handleHomeButton(focusedToken, event);
         } else if (keyCode == KeyEvent.KEYCODE_MENU) {
             // Hijack modified menu keys for debugging features
             final int chordBug = KeyEvent.META_SHIFT_ON;
@@ -3120,8 +3122,7 @@
                 || Settings.Global.getInt(mContext.getContentResolver(),
                         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1) {
             try {
-                ActivityManager.getService()
-                        .requestBugReport(ActivityManager.BUGREPORT_OPTION_FULL);
+                ActivityManager.getService().requestFullBugReport();
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error taking bugreport", e);
             }
@@ -3131,10 +3132,15 @@
     // TODO(b/117479243): handle it in InputPolicy
     /** {@inheritDoc} */
     @Override
-    public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
+    public KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags) {
         // Note: This method is only called if the initial down was unhandled.
         if (DEBUG_INPUT) {
-            Slog.d(TAG, "Unhandled key: win=" + win + ", action=" + event.getAction()
+            final KeyInterceptionInfo info =
+                    mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
+            final String title = info == null ? "<unknown>" : info.windowTitle;
+            Slog.d(TAG, "Unhandled key: inputToken=" + focusedToken
+                    + ", title=" + title
+                    + ", action=" + event.getAction()
                     + ", flags=" + event.getFlags()
                     + ", keyCode=" + event.getKeyCode()
                     + ", scanCode=" + event.getScanCode()
@@ -3173,7 +3179,7 @@
                         event.getDeviceId(), event.getScanCode(),
                         flags, event.getSource(), event.getDisplayId(), null);
 
-                if (!interceptFallback(win, fallbackEvent, policyFlags)) {
+                if (!interceptFallback(focusedToken, fallbackEvent, policyFlags)) {
                     fallbackEvent.recycle();
                     fallbackEvent = null;
                 }
@@ -3197,11 +3203,12 @@
         return fallbackEvent;
     }
 
-    private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {
+    private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
+            int policyFlags) {
         int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
         if ((actions & ACTION_PASS_TO_USER) != 0) {
             long delayMillis = interceptKeyBeforeDispatching(
-                    win, fallbackEvent, policyFlags);
+                    focusedToken, fallbackEvent, policyFlags);
             if (delayMillis == 0) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 6d9c710..01250db 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -173,7 +173,7 @@
 
     /**
      * Interface to the Window Manager state associated with a particular
-     * window.  You can hold on to an instance of this interface from the call
+     * window. You can hold on to an instance of this interface from the call
      * to prepareAddWindow() until removeWindow().
      */
     public interface WindowState {
@@ -1025,7 +1025,7 @@
      * behavior for keys that can not be overridden by applications.
      * This method is called from the input thread, with no locks held.
      *
-     * @param win The window that currently has focus.  This is where the key
+     * @param focusedToken Client window token that currently has focus. This is where the key
      *            event will normally go.
      * @param event The key event.
      * @param policyFlags The policy flags associated with the key.
@@ -1034,7 +1034,7 @@
      * milliseconds by which the key dispatch should be delayed before trying
      * again.
      */
-    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags);
+    long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event, int policyFlags);
 
     /**
      * Called from the input dispatcher thread when an application did not handle
@@ -1043,14 +1043,14 @@
      * <p>Allows you to define default global behavior for keys that were not handled
      * by applications.  This method is called from the input thread, with no locks held.
      *
-     * @param win The window that currently has focus.  This is where the key
+     * @param focusedToken Client window token that currently has focus. This is where the key
      *            event will normally go.
      * @param event The key event.
      * @param policyFlags The policy flags associated with the key.
      * @return Returns an alternate key event to redispatch as a fallback, or null to give up.
      * The caller is responsible for recycling the key event.
      */
-    public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags);
+    KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags);
 
     /**
      * Called when the top focused display is changed.
diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
index cae09ea3..3f9cc83b 100644
--- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
+++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
@@ -23,17 +23,12 @@
 import android.util.Slog;
 import android.util.SparseLongArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 /**
  * Encapsulates the logic for initiating userdata snapshots and rollbacks via installd.
@@ -56,6 +51,8 @@
      * {@code userIds}. Updates said {@code packageRollbackInfo} with the inodes of the CE user data
      * snapshot folders.
      */
+    @GuardedBy("rollback.getLock")
+    // TODO(b/136241838): Move into Rollback and synchronize there.
     public void snapshotAppData(
             int snapshotId, PackageRollbackInfo packageRollbackInfo, int[] userIds) {
         for (int user : userIds) {
@@ -92,6 +89,8 @@
      *         to {@code packageRollbackInfo} are restricted to the removal or addition of {@code
      *         userId} to the list of pending backups or restores.
      */
+    @GuardedBy("rollback.getLock")
+    // TODO(b/136241838): Move into Rollback and synchronize there.
     public boolean restoreAppData(int rollbackId, PackageRollbackInfo packageRollbackInfo,
             int userId, int appId, String seInfo) {
         int storageFlags = Installer.FLAG_STORAGE_DE;
@@ -135,6 +134,8 @@
      * Deletes an app data snapshot with a given {@code rollbackId} for a specified package
      * {@code packageName} for a given {@code user}.
      */
+    @GuardedBy("rollback.getLock")
+    // TODO(b/136241838): Move into Rollback and synchronize there.
     public void destroyAppDataSnapshot(int rollbackId, PackageRollbackInfo packageRollbackInfo,
             int user) {
         int storageFlags = Installer.FLAG_STORAGE_DE;
@@ -156,141 +157,68 @@
     }
 
     /**
-     * Computes the list of pending backups for {@code userId} given lists of rollbacks.
-     * Packages pending backup for the given user are added to {@code pendingBackupPackages} along
-     * with their corresponding {@code PackageRollbackInfo}.
+     * Commits the pending backups and restores for a given {@code userId} and {@code rollback}. If
+     * the rollback has a pending backup, it is updated with a mapping from {@code userId} to inode
+     * of the CE user data snapshot.
      *
-     * @return the list of rollbacks that have pending backups. Note that some of the
-     *         backups won't be performed, because they might be counteracted by pending restores.
+     * @return true if any backups or restores were found for the userId
      */
-    private static List<Rollback> computePendingBackups(int userId,
-            Map<String, PackageRollbackInfo> pendingBackupPackages,
-            List<Rollback> rollbacks) {
-        List<Rollback> rollbacksWithPendingBackups = new ArrayList<>();
-
-        for (Rollback rollback : rollbacks) {
-            for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                final IntArray pendingBackupUsers = info.getPendingBackups();
-                if (pendingBackupUsers != null) {
-                    final int idx = pendingBackupUsers.indexOf(userId);
-                    if (idx != -1) {
-                        pendingBackupPackages.put(info.getPackageName(), info);
-                        if (rollbacksWithPendingBackups.indexOf(rollback) == -1) {
-                            rollbacksWithPendingBackups.add(rollback);
-                        }
-                    }
+    @GuardedBy("rollback.getLock")
+    boolean commitPendingBackupAndRestoreForUser(int userId, Rollback rollback) {
+        boolean foundBackupOrRestore = false;
+        for (PackageRollbackInfo info : rollback.info.getPackages()) {
+            boolean hasPendingBackup = false;
+            boolean hasPendingRestore = false;
+            final IntArray pendingBackupUsers = info.getPendingBackups();
+            if (pendingBackupUsers != null) {
+                if (pendingBackupUsers.indexOf(userId) != -1) {
+                    hasPendingBackup = true;
+                    foundBackupOrRestore = true;
                 }
             }
-        }
-        return rollbacksWithPendingBackups;
-    }
 
-    /**
-     * Computes the list of pending restores for {@code userId} given lists of rollbacks.
-     * Packages pending restore are added to {@code pendingRestores} along with their corresponding
-     * {@code PackageRollbackInfo}.
-     *
-     * @return the list of rollbacks that have pending restores. Note that some of the
-     *         restores won't be performed, because they might be counteracted by pending backups.
-     */
-    private static List<Rollback> computePendingRestores(int userId,
-            Map<String, PackageRollbackInfo> pendingRestorePackages,
-            List<Rollback> rollbacks) {
-        List<Rollback> rollbacksWithPendingRestores = new ArrayList<>();
-
-        for (Rollback rollback : rollbacks) {
-            for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                final RestoreInfo ri = info.getRestoreInfo(userId);
-                if (ri != null) {
-                    pendingRestorePackages.put(info.getPackageName(), info);
-                    if (rollbacksWithPendingRestores.indexOf(rollback) == -1) {
-                        rollbacksWithPendingRestores.add(rollback);
-                    }
-                }
+            RestoreInfo ri = info.getRestoreInfo(userId);
+            if (ri != null) {
+                hasPendingRestore = true;
+                foundBackupOrRestore = true;
             }
-        }
 
-        return rollbacksWithPendingRestores;
-    }
-
-    /**
-     * Commits the list of pending backups and restores for a given {@code userId}. For rollbacks
-     * with pending backups, updates the {@code Rollback} instance with a mapping from
-     * {@code userId} to inode of the CE user data snapshot.
-     *
-     * @return the set of rollbacks with changes that should be stored on disk.
-     */
-    public Set<Rollback> commitPendingBackupAndRestoreForUser(int userId,
-            List<Rollback> rollbacks) {
-
-        final Map<String, PackageRollbackInfo> pendingBackupPackages = new HashMap<>();
-        final List<Rollback> pendingBackups = computePendingBackups(userId,
-                pendingBackupPackages, rollbacks);
-
-        final Map<String, PackageRollbackInfo> pendingRestorePackages = new HashMap<>();
-        final List<Rollback> pendingRestores = computePendingRestores(userId,
-                pendingRestorePackages, rollbacks);
-
-        // First remove unnecessary backups, i.e. when user did not unlock their phone between the
-        // request to backup data and the request to restore it.
-        Iterator<Map.Entry<String, PackageRollbackInfo>> iter =
-                pendingBackupPackages.entrySet().iterator();
-        while (iter.hasNext()) {
-            PackageRollbackInfo backupPackage = iter.next().getValue();
-            PackageRollbackInfo restorePackage =
-                    pendingRestorePackages.get(backupPackage.getPackageName());
-            if (restorePackage != null) {
-                backupPackage.removePendingBackup(userId);
-                backupPackage.removePendingRestoreInfo(userId);
-                iter.remove();
-                pendingRestorePackages.remove(backupPackage.getPackageName());
+            if (hasPendingBackup && hasPendingRestore) {
+                // Remove unnecessary backup, i.e. when user did not unlock their phone between the
+                // request to backup data and the request to restore it.
+                info.removePendingBackup(userId);
+                info.removePendingRestoreInfo(userId);
+                continue;
             }
-        }
 
-        if (!pendingBackupPackages.isEmpty()) {
-            for (Rollback rollback : pendingBackups) {
-                for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                    final IntArray pendingBackupUsers = info.getPendingBackups();
-                    final int idx = pendingBackupUsers.indexOf(userId);
-                    if (idx != -1) {
-                        try {
-                            long ceSnapshotInode = mInstaller.snapshotAppData(info.getPackageName(),
-                                    userId, rollback.info.getRollbackId(),
-                                    Installer.FLAG_STORAGE_CE);
-                            info.putCeSnapshotInode(userId, ceSnapshotInode);
-                            pendingBackupUsers.remove(idx);
-                        } catch (InstallerException ie) {
-                            Slog.e(TAG,
-                                    "Unable to create app data snapshot for: "
+            if (hasPendingBackup) {
+                int idx = pendingBackupUsers.indexOf(userId);
+                try {
+                    long ceSnapshotInode = mInstaller.snapshotAppData(info.getPackageName(),
+                            userId, rollback.info.getRollbackId(),
+                            Installer.FLAG_STORAGE_CE);
+                    info.putCeSnapshotInode(userId, ceSnapshotInode);
+                    pendingBackupUsers.remove(idx);
+                } catch (InstallerException ie) {
+                    Slog.e(TAG,
+                            "Unable to create app data snapshot for: "
                                     + info.getPackageName() + ", userId: " + userId, ie);
-                        }
-                    }
+                }
+            }
+
+            if (hasPendingRestore) {
+                try {
+                    mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId,
+                            ri.seInfo, userId, rollback.info.getRollbackId(),
+                            Installer.FLAG_STORAGE_CE);
+                    info.removeRestoreInfo(ri);
+                } catch (InstallerException ie) {
+                    Slog.e(TAG, "Unable to restore app data snapshot for: "
+                            + info.getPackageName(), ie);
                 }
             }
         }
-
-        if (!pendingRestorePackages.isEmpty()) {
-            for (Rollback rollback : pendingRestores) {
-                for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                    final RestoreInfo ri = info.getRestoreInfo(userId);
-                    if (ri != null) {
-                        try {
-                            mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId,
-                                    ri.seInfo, userId, rollback.info.getRollbackId(),
-                                    Installer.FLAG_STORAGE_CE);
-                            info.removeRestoreInfo(ri);
-                        } catch (InstallerException ie) {
-                            Slog.e(TAG, "Unable to restore app data snapshot for: "
-                                    + info.getPackageName(), ie);
-                        }
-                    }
-                }
-            }
-        }
-
-        final Set<Rollback> changed = new HashSet<>(pendingBackups);
-        changed.addAll(pendingRestores);
-        return changed;
+        return foundBackupOrRestore;
     }
 
     /**
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 6769fe0..2dc4951 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -18,19 +18,27 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.content.rollback.PackageRollbackInfo;
 import android.content.rollback.RollbackInfo;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.File;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.text.ParseException;
 import java.time.Instant;
 import java.util.ArrayList;
+import java.util.List;
 
 
 /**
- * Information about a rollback available for a set of atomically installed
- * packages.
+ * Information about a rollback available for a set of atomically installed packages.
+ *
+ * <p>When accessing the state of a Rollback object, the caller is responsible for synchronization.
+ * The lock object provided by {@link #getLock} should be acquired when accessing any of the mutable
+ * state of a Rollback, including from the {@link RollbackInfo} and any of the
+ * {@link PackageRollbackInfo} objects held within.
  */
 class Rollback {
     @IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = {
@@ -58,8 +66,18 @@
     static final int ROLLBACK_STATE_COMMITTED = 3;
 
     /**
-     * The rollback info for this rollback.
+     * The session ID for the staged session if this rollback data represents a staged session,
+     * {@code -1} otherwise.
      */
+    private final int mStagedSessionId;
+
+    /**
+     * The rollback info for this rollback.
+     *
+     * <p>Any access to this field that touches any mutable state should be synchronized on
+     * {@link #getLock}.
+     */
+    @GuardedBy("getLock")
     public final RollbackInfo info;
 
     /**
@@ -74,23 +92,20 @@
      * The timestamp is not applicable for all rollback states, but we make
      * sure to keep it non-null to avoid potential errors there.
      */
+    @GuardedBy("mLock")
     private @NonNull Instant mTimestamp;
 
     /**
-     * The session ID for the staged session if this rollback data represents a staged session,
-     * {@code -1} otherwise.
-     */
-    private final int mStagedSessionId;
-
-    /**
      * The current state of the rollback.
      * ENABLING, AVAILABLE, or COMMITTED.
      */
+    @GuardedBy("mLock")
     private @RollbackState int mState;
 
     /**
      * The id of the post-reboot apk session for a staged install, if any.
      */
+    @GuardedBy("mLock")
     private int mApkSessionId = -1;
 
     /**
@@ -98,10 +113,17 @@
      * for this rollback because it has just been committed but the rollback
      * has not yet been fully applied.
      */
-    // NOTE: All accesses to this field are from the RollbackManager handler thread.
+    @GuardedBy("mLock")
     private boolean mRestoreUserDataInProgress = false;
 
     /**
+     * Lock object to guard all access to Rollback state.
+     *
+     * @see #getLock
+     */
+    private final Object mLock = new Object();
+
+    /**
      * Constructs a new, empty Rollback instance.
      *
      * @param rollbackId the id of the rollback.
@@ -135,8 +157,23 @@
     }
 
     /**
+     * Returns a lock object that should be acquired before accessing any Rollback state from
+     * {@link RollbackManagerServiceImpl}.
+     *
+     * <p>Note that while holding this lock, the lock for {@link RollbackManagerServiceImpl} should
+     * not be acquired (but it is ok to acquire this lock while already holding the lock for that
+     * class).
+     */
+    // TODO(b/136241838): Move rollback functionality into this class and synchronize on the lock
+    // internally. Remove this method once this has been done for all cases.
+    Object getLock() {
+        return mLock;
+    }
+
+    /**
      * Whether the rollback is for rollback of a staged install.
      */
+    @GuardedBy("getLock")
     boolean isStaged() {
         return info.isStaged();
     }
@@ -151,6 +188,7 @@
     /**
      * Returns the time when the upgrade occurred, for purposes of expiring rollback data.
      */
+    @GuardedBy("getLock")
     Instant getTimestamp() {
         return mTimestamp;
     }
@@ -158,6 +196,7 @@
     /**
      * Sets the time at which upgrade occurred.
      */
+    @GuardedBy("getLock")
     void setTimestamp(Instant timestamp) {
         mTimestamp = timestamp;
     }
@@ -173,6 +212,7 @@
     /**
      * Returns true if the rollback is in the ENABLING state.
      */
+    @GuardedBy("getLock")
     boolean isEnabling() {
         return mState == ROLLBACK_STATE_ENABLING;
     }
@@ -180,6 +220,7 @@
     /**
      * Returns true if the rollback is in the AVAILABLE state.
      */
+    @GuardedBy("getLock")
     boolean isAvailable() {
         return mState == ROLLBACK_STATE_AVAILABLE;
     }
@@ -187,6 +228,7 @@
     /**
      * Returns true if the rollback is in the COMMITTED state.
      */
+    @GuardedBy("getLock")
     boolean isCommitted() {
         return mState == ROLLBACK_STATE_COMMITTED;
     }
@@ -194,6 +236,7 @@
     /**
      * Sets the state of the rollback to AVAILABLE.
      */
+    @GuardedBy("getLock")
     void setAvailable() {
         mState = ROLLBACK_STATE_AVAILABLE;
     }
@@ -201,6 +244,7 @@
     /**
      * Sets the state of the rollback to COMMITTED.
      */
+    @GuardedBy("getLock")
     void setCommitted() {
         mState = ROLLBACK_STATE_COMMITTED;
     }
@@ -208,6 +252,7 @@
     /**
      * Returns the id of the post-reboot apk session for a staged install, if any.
      */
+    @GuardedBy("getLock")
     int getApkSessionId() {
         return mApkSessionId;
     }
@@ -215,6 +260,7 @@
     /**
      * Sets the id of the post-reboot apk session for a staged install.
      */
+    @GuardedBy("getLock")
     void setApkSessionId(int apkSessionId) {
         mApkSessionId = apkSessionId;
     }
@@ -223,6 +269,7 @@
      * Returns true if we are expecting the package manager to call restoreUserData for this
      * rollback because it has just been committed but the rollback has not yet been fully applied.
      */
+    @GuardedBy("getLock")
     boolean isRestoreUserDataInProgress() {
         return mRestoreUserDataInProgress;
     }
@@ -231,10 +278,65 @@
      * Sets whether we are expecting the package manager to call restoreUserData for this
      * rollback because it has just been committed but the rollback has not yet been fully applied.
      */
+    @GuardedBy("getLock")
     void setRestoreUserDataInProgress(boolean restoreUserDataInProgress) {
         mRestoreUserDataInProgress = restoreUserDataInProgress;
     }
 
+    /**
+     * Returns true if this rollback includes the package with the provided {@code packageName}.
+     */
+    @GuardedBy("getLock")
+    boolean includesPackage(String packageName) {
+        for (PackageRollbackInfo info : info.getPackages()) {
+            if (info.getPackageName().equals(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this rollback includes the package with the provided {@code packageName}
+     * with a <i>version rolled back from</i> that is not {@code versionCode}.
+     */
+    @GuardedBy("getLock")
+    boolean includesPackageWithDifferentVersion(String packageName, long versionCode) {
+        for (PackageRollbackInfo info : info.getPackages()) {
+            if (info.getPackageName().equals(packageName)
+                    && info.getVersionRolledBackFrom().getLongVersionCode() != versionCode) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns a list containing the names of all the packages included in this rollback.
+     */
+    @GuardedBy("getLock")
+    List<String> getPackageNames() {
+        List<String> result = new ArrayList<>();
+        for (PackageRollbackInfo info : info.getPackages()) {
+            result.add(info.getPackageName());
+        }
+        return result;
+    }
+
+    /**
+     * Returns a list containing the names of all the apex packages included in this rollback.
+     */
+    @GuardedBy("getLock")
+    List<String> getApexPackageNames() {
+        List<String> result = new ArrayList<>();
+        for (PackageRollbackInfo info : info.getPackages()) {
+            if (info.isApex()) {
+                result.add(info.getPackageName());
+            }
+        }
+        return result;
+    }
+
     static String rollbackStateToString(@RollbackState int state) {
         switch (state) {
             case Rollback.ROLLBACK_STATE_ENABLING: return "enabling";
@@ -254,6 +356,7 @@
         throw new ParseException("Invalid rollback state: " + state, 0);
     }
 
+    @GuardedBy("getLock")
     String getStateAsString() {
         return rollbackStateToString(mState);
     }
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 96d284b..e8e448a 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -282,8 +282,10 @@
             List<RollbackInfo> rollbacks = new ArrayList<>();
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback rollback = mRollbacks.get(i);
-                if (rollback.isAvailable()) {
-                    rollbacks.add(rollback.info);
+                synchronized (rollback.getLock()) {
+                    if (rollback.isAvailable()) {
+                        rollbacks.add(rollback.info);
+                    }
                 }
             }
             return new ParceledListSlice<>(rollbacks);
@@ -298,8 +300,10 @@
             List<RollbackInfo> rollbacks = new ArrayList<>();
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback rollback = mRollbacks.get(i);
-                if (rollback.isCommitted()) {
-                    rollbacks.add(rollback.info);
+                synchronized (rollback.getLock()) {
+                    if (rollback.isCommitted()) {
+                        rollbacks.add(rollback.info);
+                    }
                 }
             }
             return new ParceledListSlice<>(rollbacks);
@@ -332,8 +336,11 @@
                     Iterator<Rollback> iter = mRollbacks.iterator();
                     while (iter.hasNext()) {
                         Rollback rollback = iter.next();
-                        rollback.setTimestamp(rollback.getTimestamp().plusMillis(timeDifference));
-                        saveRollback(rollback);
+                        synchronized (rollback.getLock()) {
+                            rollback.setTimestamp(
+                                    rollback.getTimestamp().plusMillis(timeDifference));
+                            saveRollback(rollback);
+                        }
                     }
                 }
             }
@@ -358,86 +365,94 @@
         Slog.i(TAG, "Initiating rollback");
 
         Rollback rollback = getRollbackForId(rollbackId);
-        if (rollback == null || !rollback.isAvailable()) {
+        if (rollback == null) {
             sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
                     "Rollback unavailable");
             return;
         }
-
-        // Get a context for the caller to use to install the downgraded
-        // version of the package.
-        final Context context;
-        try {
-            context = mContext.createPackageContext(callerPackageName, 0);
-        } catch (PackageManager.NameNotFoundException e) {
-            sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
-                    "Invalid callerPackageName");
-            return;
-        }
-
-        PackageManager pm = context.getPackageManager();
-        try {
-            PackageInstaller packageInstaller = pm.getPackageInstaller();
-            PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
-                    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-            parentParams.setRequestDowngrade(true);
-            parentParams.setMultiPackage();
-            if (rollback.isStaged()) {
-                parentParams.setStaged();
+        synchronized (rollback.getLock()) {
+            if (!rollback.isAvailable()) {
+                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
+                        "Rollback unavailable");
+                return;
             }
 
-            int parentSessionId = packageInstaller.createSession(parentParams);
-            PackageInstaller.Session parentSession = packageInstaller.openSession(parentSessionId);
+            // Get a context for the caller to use to install the downgraded
+            // version of the package.
+            final Context context;
+            try {
+                context = mContext.createPackageContext(callerPackageName, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
+                        "Invalid callerPackageName");
+                return;
+            }
 
-            for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+            PackageManager pm = context.getPackageManager();
+            try {
+                PackageInstaller packageInstaller = pm.getPackageInstaller();
+                PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
                         PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-                // TODO: We can't get the installerPackageName for apex
-                // (b/123920130). Is it okay to ignore the installer package
-                // for apex?
-                if (!info.isApex()) {
-                    String installerPackageName = pm.getInstallerPackageName(info.getPackageName());
-                    if (installerPackageName != null) {
-                        params.setInstallerPackageName(installerPackageName);
-                    }
-                }
-                params.setRequestDowngrade(true);
-                params.setRequiredInstalledVersionCode(
-                        info.getVersionRolledBackFrom().getLongVersionCode());
+                parentParams.setRequestDowngrade(true);
+                parentParams.setMultiPackage();
                 if (rollback.isStaged()) {
-                    params.setStaged();
-                }
-                if (info.isApex()) {
-                    params.setInstallAsApex();
-                }
-                int sessionId = packageInstaller.createSession(params);
-                PackageInstaller.Session session = packageInstaller.openSession(sessionId);
-                File[] packageCodePaths = RollbackStore.getPackageCodePaths(
-                        rollback, info.getPackageName());
-                if (packageCodePaths == null) {
-                    sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
-                            "Backup copy of package inaccessible");
-                    return;
+                    parentParams.setStaged();
                 }
 
-                for (File packageCodePath : packageCodePaths) {
-                    try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCodePath,
-                                ParcelFileDescriptor.MODE_READ_ONLY)) {
-                        final long token = Binder.clearCallingIdentity();
-                        try {
-                            session.write(packageCodePath.getName(), 0, packageCodePath.length(),
-                                    fd);
-                        } finally {
-                            Binder.restoreCallingIdentity(token);
+                int parentSessionId = packageInstaller.createSession(parentParams);
+                PackageInstaller.Session parentSession = packageInstaller.openSession(
+                        parentSessionId);
+
+                for (PackageRollbackInfo info : rollback.info.getPackages()) {
+                    PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                            PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+                    // TODO: We can't get the installerPackageName for apex
+                    // (b/123920130). Is it okay to ignore the installer package
+                    // for apex?
+                    if (!info.isApex()) {
+                        String installerPackageName =
+                                pm.getInstallerPackageName(info.getPackageName());
+                        if (installerPackageName != null) {
+                            params.setInstallerPackageName(installerPackageName);
                         }
                     }
-                }
-                parentSession.addChildSessionId(sessionId);
-            }
+                    params.setRequestDowngrade(true);
+                    params.setRequiredInstalledVersionCode(
+                            info.getVersionRolledBackFrom().getLongVersionCode());
+                    if (rollback.isStaged()) {
+                        params.setStaged();
+                    }
+                    if (info.isApex()) {
+                        params.setInstallAsApex();
+                    }
+                    int sessionId = packageInstaller.createSession(params);
+                    PackageInstaller.Session session = packageInstaller.openSession(sessionId);
+                    File[] packageCodePaths = RollbackStore.getPackageCodePaths(
+                            rollback, info.getPackageName());
+                    if (packageCodePaths == null) {
+                        sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
+                                "Backup copy of package inaccessible");
+                        return;
+                    }
 
-            final LocalIntentReceiver receiver = new LocalIntentReceiver(
-                    (Intent result) -> {
-                        getHandler().post(() -> {
+                    for (File packageCodePath : packageCodePaths) {
+                        try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCodePath,
+                                ParcelFileDescriptor.MODE_READ_ONLY)) {
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                session.write(packageCodePath.getName(), 0,
+                                        packageCodePath.length(),
+                                        fd);
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
+                        }
+                    }
+                    parentSession.addChildSessionId(sessionId);
+                }
+
+                final LocalIntentReceiver receiver = new LocalIntentReceiver(
+                        (Intent result) -> getHandler().post(() -> {
 
                             int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                                     PackageInstaller.STATUS_FAILURE);
@@ -450,21 +465,22 @@
                                 // TODO: Should we just kill this rollback if
                                 // commit failed? Why would we expect commit
                                 // not to fail again?
-                                synchronized (mLock) {
-                                    // TODO: Could this cause a rollback to be
-                                    // resurrected if it should otherwise have
-                                    // expired by now?
+                                // TODO: Could this cause a rollback to be
+                                // resurrected if it should otherwise have
+                                // expired by now?
+                                synchronized (rollback.getLock()) {
                                     rollback.setAvailable();
                                     rollback.setRestoreUserDataInProgress(false);
                                 }
-                                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_INSTALL,
+                                sendFailure(statusReceiver,
+                                        RollbackManager.STATUS_FAILURE_INSTALL,
                                         "Rollback downgrade install failed: "
-                                        + result.getStringExtra(
+                                                + result.getStringExtra(
                                                 PackageInstaller.EXTRA_STATUS_MESSAGE));
                                 return;
                             }
 
-                            synchronized (mLock) {
+                            synchronized (rollback.getLock()) {
                                 if (!rollback.isStaged()) {
                                     // All calls to restoreUserData should have
                                     // completed by now for a non-staged install.
@@ -473,32 +489,31 @@
 
                                 rollback.info.setCommittedSessionId(parentSessionId);
                                 rollback.info.getCausePackages().addAll(causePackages);
+                                RollbackStore.deletePackageCodePaths(rollback);
+                                saveRollback(rollback);
                             }
-                            mRollbackStore.deletePackageCodePaths(rollback);
-                            saveRollback(rollback);
 
                             sendSuccess(statusReceiver);
 
                             Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
 
                             for (UserInfo userInfo : UserManager.get(mContext).getUsers(true)) {
-                                mContext.sendBroadcastAsUser(broadcast, userInfo.getUserHandle(),
+                                mContext.sendBroadcastAsUser(broadcast,
+                                        userInfo.getUserHandle(),
                                         Manifest.permission.MANAGE_ROLLBACKS);
                             }
-                        });
-                    }
-            );
+                        })
+                );
 
-            synchronized (mLock) {
                 rollback.setCommitted();
                 rollback.setRestoreUserDataInProgress(true);
+                parentSession.commit(receiver.getIntentSender());
+            } catch (IOException e) {
+                Slog.e(TAG, "Rollback failed", e);
+                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
+                        "IOException: " + e.toString());
+                return;
             }
-            parentSession.commit(receiver.getIntentSender());
-        } catch (IOException e) {
-            Slog.e(TAG, "Rollback failed", e);
-            sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
-                    "IOException: " + e.toString());
-            return;
         }
     }
 
@@ -534,19 +549,17 @@
             Iterator<Rollback> iter = mRollbacks.iterator();
             while (iter.hasNext()) {
                 Rollback rollback = iter.next();
-                for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                    if (info.getPackageName().equals(packageName)) {
+                synchronized (rollback.getLock()) {
+                    if (rollback.includesPackage(packageName)) {
                         iter.remove();
                         deleteRollback(rollback);
-                        break;
                     }
                 }
             }
             for (NewRollback newRollback : mNewRollbacks) {
-                for (PackageRollbackInfo info : newRollback.rollback.info.getPackages()) {
-                    if (info.getPackageName().equals(packageName)) {
+                synchronized (newRollback.rollback.getLock()) {
+                    if (newRollback.rollback.includesPackage(packageName)) {
                         newRollback.isCancelled = true;
-                        break;
                     }
                 }
             }
@@ -578,12 +591,16 @@
                 rollbacks = new ArrayList<>(mRollbacks);
             }
 
-            final Set<Rollback> changed =
-                    mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(userId, rollbacks);
-
-            for (Rollback rollback : changed) {
-                saveRollback(rollback);
+            for (int i = 0; i < rollbacks.size(); i++) {
+                Rollback rollback = rollbacks.get(i);
+                synchronized (rollback.getLock()) {
+                    if (mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(
+                            userId, rollback)) {
+                        saveRollback(rollback);
+                    }
+                }
             }
+
             latch.countDown();
         });
 
@@ -617,17 +634,15 @@
             Set<String> apexPackageNames = new HashSet<>();
             synchronized (mLock) {
                 for (Rollback rollback : mRollbacks) {
-                    if (rollback.isStaged()) {
-                        if (rollback.isEnabling()) {
-                            enabling.add(rollback);
-                        } else if (rollback.isRestoreUserDataInProgress()) {
-                            restoreInProgress.add(rollback);
-                        }
-
-                        for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                            if (info.isApex()) {
-                                apexPackageNames.add(info.getPackageName());
+                    synchronized (rollback.getLock()) {
+                        if (rollback.isStaged()) {
+                            if (rollback.isEnabling()) {
+                                enabling.add(rollback);
+                            } else if (rollback.isRestoreUserDataInProgress()) {
+                                restoreInProgress.add(rollback);
                             }
+
+                            apexPackageNames.addAll(rollback.getApexPackageNames());
                         }
                     }
                 }
@@ -635,30 +650,32 @@
 
             for (Rollback rollback : enabling) {
                 PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
-                PackageInstaller.SessionInfo session =
-                        installer.getSessionInfo(rollback.getStagedSessionId());
-                if (session == null || session.isStagedSessionFailed()) {
-                    // TODO: Do we need to remove this from
-                    // mRollbacks, or is it okay to leave as
-                    // unavailable until the next reboot when it will go
-                    // away on its own?
-                    deleteRollback(rollback);
-                } else if (session.isStagedSessionApplied()) {
-                    makeRollbackAvailable(rollback);
+                synchronized (rollback.getLock()) {
+                    PackageInstaller.SessionInfo session =
+                            installer.getSessionInfo(rollback.getStagedSessionId());
+                    if (session == null || session.isStagedSessionFailed()) {
+                        // TODO: Do we need to remove this from
+                        // mRollbacks, or is it okay to leave as
+                        // unavailable until the next reboot when it will go
+                        // away on its own?
+                        deleteRollback(rollback);
+                    } else if (session.isStagedSessionApplied()) {
+                        makeRollbackAvailable(rollback);
+                    }
                 }
             }
 
             for (Rollback rollback : restoreInProgress) {
                 PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
-                PackageInstaller.SessionInfo session =
-                        installer.getSessionInfo(rollback.getStagedSessionId());
-                // TODO: What if session is null?
-                if (session != null) {
-                    if (session.isStagedSessionApplied() || session.isStagedSessionFailed()) {
-                        synchronized (mLock) {
+                synchronized (rollback.getLock()) {
+                    PackageInstaller.SessionInfo session =
+                            installer.getSessionInfo(rollback.getStagedSessionId());
+                    // TODO: What if session is null?
+                    if (session != null) {
+                        if (session.isStagedSessionApplied() || session.isStagedSessionFailed()) {
                             rollback.setRestoreUserDataInProgress(false);
+                            saveRollback(rollback);
                         }
-                        saveRollback(rollback);
                     }
                 }
             }
@@ -687,23 +704,19 @@
     private void onPackageReplaced(String packageName) {
         // TODO: Could this end up incorrectly deleting a rollback for a
         // package that is about to be installed?
-        VersionedPackage installedVersion = getInstalledPackageVersion(packageName);
+        long installedVersion = getInstalledPackageVersion(packageName);
 
         synchronized (mLock) {
             Iterator<Rollback> iter = mRollbacks.iterator();
             while (iter.hasNext()) {
                 Rollback rollback = iter.next();
-                // TODO: Should we remove rollbacks in the ENABLING state here?
-                if (rollback.isEnabling() || rollback.isAvailable()) {
-                    for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                        if (info.getPackageName().equals(packageName)
-                                && !packageVersionsEqual(
-                                    info.getVersionRolledBackFrom(),
-                                    installedVersion)) {
-                            iter.remove();
-                            deleteRollback(rollback);
-                            break;
-                        }
+                synchronized (rollback.getLock()) {
+                    // TODO: Should we remove rollbacks in the ENABLING state here?
+                    if ((rollback.isEnabling() || rollback.isAvailable())
+                            && rollback.includesPackageWithDifferentVersion(packageName,
+                            installedVersion)) {
+                        iter.remove();
+                        deleteRollback(rollback);
                     }
                 }
             }
@@ -760,16 +773,18 @@
             Iterator<Rollback> iter = mRollbacks.iterator();
             while (iter.hasNext()) {
                 Rollback rollback = iter.next();
-                if (!rollback.isAvailable()) {
-                    continue;
-                }
-                if (!now.isBefore(
+                synchronized (rollback.getLock()) {
+                    if (!rollback.isAvailable()) {
+                        continue;
+                    }
+                    if (!now.isBefore(
                             rollback.getTimestamp()
                                     .plusMillis(mRollbackLifetimeDurationInMillis))) {
-                    iter.remove();
-                    deleteRollback(rollback);
-                } else if (oldest == null || oldest.isAfter(rollback.getTimestamp())) {
-                    oldest = rollback.getTimestamp();
+                        iter.remove();
+                        deleteRollback(rollback);
+                    } else if (oldest == null || oldest.isAfter(rollback.getTimestamp())) {
+                        oldest = rollback.getTimestamp();
+                    }
                 }
             }
         }
@@ -877,10 +892,12 @@
         synchronized (mLock) {
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback rollback = mRollbacks.get(i);
-                if (rollback.getApkSessionId() == parentSession.getSessionId()) {
-                    // This is the apk session for a staged session with rollback enabled. We do not
-                    // need to create a new rollback for this session.
-                    return true;
+                synchronized (rollback.getLock()) {
+                    if (rollback.getApkSessionId() == parentSession.getSessionId()) {
+                        // This is the apk session for a staged session with rollback enabled. We do
+                        // not need to create a new rollback for this session.
+                        return true;
+                    }
                 }
             }
         }
@@ -979,6 +996,7 @@
                 new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
                 isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */);
 
+
         try {
             ApplicationInfo appInfo = pkgInfo.applicationInfo;
             RollbackStore.backupPackageCodePath(rollback, packageName, appInfo.sourceDir);
@@ -992,7 +1010,7 @@
             return false;
         }
 
-        synchronized (mLock) {
+        synchronized (rollback.getLock()) {
             rollback.info.getPackages().add(packageRollbackInfo);
         }
         return true;
@@ -1020,27 +1038,31 @@
             // staged installs
             for (int i = 0; i < mRollbacks.size(); i++) {
                 Rollback rollback = mRollbacks.get(i);
-                if (!rollback.isEnabling()) {
-                    continue;
-                }
+                synchronized (rollback.getLock()) {
+                    if (!rollback.isEnabling()) {
+                        continue;
+                    }
 
-                for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                    if (info.getPackageName().equals(packageName)) {
-                        mAppDataRollbackHelper.snapshotAppData(
-                                rollback.info.getRollbackId(), info, userIds);
-                        saveRollback(rollback);
-                        break;
+                    for (PackageRollbackInfo info : rollback.info.getPackages()) {
+                        if (info.getPackageName().equals(packageName)) {
+                            mAppDataRollbackHelper.snapshotAppData(
+                                    rollback.info.getRollbackId(), info, userIds);
+                            saveRollback(rollback);
+                            break;
+                        }
                     }
                 }
             }
             // non-staged installs
             PackageRollbackInfo info;
             for (NewRollback rollback : mNewRollbacks) {
-                info = getPackageRollbackInfo(rollback.rollback, packageName);
-                if (info != null) {
-                    mAppDataRollbackHelper.snapshotAppData(
-                            rollback.rollback.info.getRollbackId(), info, userIds);
-                    saveRollback(rollback.rollback);
+                synchronized (rollback.rollback.getLock()) {
+                    info = getPackageRollbackInfo(rollback.rollback, packageName);
+                    if (info != null) {
+                        mAppDataRollbackHelper.snapshotAppData(
+                                rollback.rollback.info.getRollbackId(), info, userIds);
+                        saveRollback(rollback.rollback);
+                    }
                 }
             }
         }
@@ -1053,11 +1075,13 @@
         synchronized (mLock) {
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback candidate = mRollbacks.get(i);
-                if (candidate.isRestoreUserDataInProgress()) {
-                    info = getPackageRollbackInfo(candidate, packageName);
-                    if (info != null) {
-                        rollback = candidate;
-                        break;
+                synchronized (candidate.getLock()) {
+                    if (candidate.isRestoreUserDataInProgress()) {
+                        info = getPackageRollbackInfo(candidate, packageName);
+                        if (info != null) {
+                            rollback = candidate;
+                            break;
+                        }
                     }
                 }
             }
@@ -1068,12 +1092,14 @@
         }
 
         for (int userId : userIds) {
-            final boolean changedRollback = mAppDataRollbackHelper.restoreAppData(
-                    rollback.info.getRollbackId(), info, userId, appId, seInfo);
+            synchronized (rollback.getLock()) {
+                final boolean changedRollback = mAppDataRollbackHelper.restoreAppData(
+                        rollback.info.getRollbackId(), info, userId, appId, seInfo);
 
-            // We've updated metadata about this rollback, so save it to flash.
-            if (changedRollback) {
-                saveRollback(rollback);
+                // We've updated metadata about this rollback, so save it to flash.
+                if (changedRollback) {
+                    saveRollback(rollback);
+                }
             }
         }
     }
@@ -1147,7 +1173,6 @@
                 for (int i = 0; i < mRollbacks.size(); ++i) {
                     Rollback candidate = mRollbacks.get(i);
                     if (candidate.getStagedSessionId() == originalSessionId) {
-                        candidate.setApkSessionId(apkSessionId);
                         rollback = candidate;
                         break;
                     }
@@ -1162,7 +1187,10 @@
             }
 
             if (rollback != null) {
-                saveRollback(rollback);
+                synchronized (rollback.getLock()) {
+                    rollback.setApkSessionId(apkSessionId);
+                    saveRollback(rollback);
+                }
             }
         });
     }
@@ -1207,18 +1235,18 @@
 
     /**
      * Gets the version of the package currently installed.
-     * Returns null if the package is not currently installed.
+     * Returns -1 if the package is not currently installed.
      */
-    private VersionedPackage getInstalledPackageVersion(String packageName) {
+    private long getInstalledPackageVersion(String packageName) {
         PackageManager pm = mContext.getPackageManager();
         PackageInfo pkgInfo = null;
         try {
             pkgInfo = getPackageInfo(packageName);
         } catch (PackageManager.NameNotFoundException e) {
-            return null;
+            return -1;
         }
 
-        return new VersionedPackage(packageName, pkgInfo.getLongVersionCode());
+        return pkgInfo.getLongVersionCode();
     }
 
     /**
@@ -1273,44 +1301,49 @@
 
             if (newRollback != null) {
                 Rollback rollback = completeEnableRollback(newRollback, success);
-                if (rollback != null && !rollback.isStaged()) {
-                    makeRollbackAvailable(rollback);
+                if (rollback != null) {
+                    synchronized (rollback.getLock()) {
+                        if (!rollback.isStaged()) {
+                            makeRollbackAvailable(rollback);
+                        }
+                    }
                 }
             }
         }
     }
 
     /**
-     * Add a rollback to the list of rollbacks.
-     * This should be called after rollback has been enabled for all packages
-     * in the rollback. It does not make the rollback available yet.
+     * Add a rollback to the list of rollbacks. This should be called after rollback has been
+     * enabled for all packages in the rollback. It does not make the rollback available yet.
+     *
+     * <p>Note that no rollback-specific locks should be held when this method is called.
      *
      * @return the Rollback instance for a successfully enable-completed rollback,
      * or null on error.
      */
     private Rollback completeEnableRollback(NewRollback newRollback, boolean success) {
         Rollback rollback = newRollback.rollback;
-        if (!success) {
-            // The install session was aborted, clean up the pending install.
-            deleteRollback(rollback);
-            return null;
-        }
-        if (newRollback.isCancelled) {
-            Slog.e(TAG, "Rollback has been cancelled by PackageManager");
-            deleteRollback(rollback);
-            return null;
-        }
+        synchronized (rollback.getLock()) {
+            if (!success) {
+                // The install session was aborted, clean up the pending install.
+                deleteRollback(rollback);
+                return null;
+            }
+            if (newRollback.isCancelled) {
+                Slog.e(TAG, "Rollback has been cancelled by PackageManager");
+                deleteRollback(rollback);
+                return null;
+            }
 
-        // It's safe to access rollback.info outside a synchronized block because
-        // this is running on the handler thread and all changes to the
-        // rollback.info occur on the handler thread.
-        if (rollback.info.getPackages().size() != newRollback.packageSessionIds.length) {
-            Slog.e(TAG, "Failed to enable rollback for all packages in session.");
-            deleteRollback(rollback);
-            return null;
-        }
 
-        saveRollback(rollback);
+            if (rollback.info.getPackages().size() != newRollback.packageSessionIds.length) {
+                Slog.e(TAG, "Failed to enable rollback for all packages in session.");
+                deleteRollback(rollback);
+                return null;
+            }
+
+            saveRollback(rollback);
+        }
         synchronized (mLock) {
             // Note: There is a small window of time between when
             // the session has been committed by the package
@@ -1328,14 +1361,13 @@
         return rollback;
     }
 
+    @GuardedBy("rollback.getLock")
     private void makeRollbackAvailable(Rollback rollback) {
         // TODO: What if the rollback has since been expired, for example due
         // to a new package being installed. Won't this revive an expired
         // rollback? Consider adding a ROLLBACK_STATE_EXPIRED to address this.
-        synchronized (mLock) {
-            rollback.setAvailable();
-            rollback.setTimestamp(Instant.now());
-        }
+        rollback.setAvailable();
+        rollback.setTimestamp(Instant.now());
         saveRollback(rollback);
 
         // TODO(zezeozue): Provide API to explicitly start observing instead
@@ -1343,11 +1375,7 @@
         // should document in PackageInstaller.SessionParams#setEnableRollback
         // After enabling and commiting any rollback, observe packages and
         // prepare to rollback if packages crashes too frequently.
-        List<String> packages = new ArrayList<>();
-        for (int i = 0; i < rollback.info.getPackages().size(); i++) {
-            packages.add(rollback.info.getPackages().get(i).getPackageName());
-        }
-        mPackageHealthObserver.startObservingHealth(packages,
+        mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
                 mRollbackLifetimeDurationInMillis);
         scheduleExpiration(mRollbackLifetimeDurationInMillis);
     }
@@ -1372,6 +1400,7 @@
      * Returns the {@code PackageRollbackInfo} associated with {@code packageName} from
      * a specified {@code Rollback}.
      */
+    @GuardedBy("rollback.getLock")
     private static PackageRollbackInfo getPackageRollbackInfo(Rollback rollback,
             String packageName) {
         for (PackageRollbackInfo info : rollback.info.getPackages()) {
@@ -1398,6 +1427,7 @@
         throw new IllegalStateException("Failed to allocate rollback ID");
     }
 
+    @GuardedBy("rollback.getLock")
     private void deleteRollback(Rollback rollback) {
         for (PackageRollbackInfo info : rollback.info.getPackages()) {
             IntArray snapshottedUsers = info.getSnapshottedUsers();
@@ -1416,6 +1446,7 @@
      * TODO: Double check we can't do a better job handling the IOException in
      * a cases where this method is called.
      */
+    @GuardedBy("rollback.getLock")
     private void saveRollback(Rollback rollback) {
         try {
             mRollbackStore.saveRollback(rollback);
@@ -1430,32 +1461,34 @@
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         synchronized (mLock) {
             for (Rollback rollback : mRollbacks) {
-                RollbackInfo info = rollback.info;
-                ipw.println(info.getRollbackId() + ":");
-                ipw.increaseIndent();
-                ipw.println("-state: " + rollback.getStateAsString());
-                ipw.println("-timestamp: " + rollback.getTimestamp());
-                if (rollback.getStagedSessionId() != -1) {
-                    ipw.println("-stagedSessionId: " + rollback.getStagedSessionId());
-                }
-                ipw.println("-packages:");
-                ipw.increaseIndent();
-                for (PackageRollbackInfo pkg : info.getPackages()) {
-                    ipw.println(pkg.getPackageName()
-                            + " " + pkg.getVersionRolledBackFrom().getLongVersionCode()
-                            + " -> " + pkg.getVersionRolledBackTo().getLongVersionCode());
-                }
-                ipw.decreaseIndent();
-                if (rollback.isCommitted()) {
-                    ipw.println("-causePackages:");
+                synchronized (rollback.getLock()) {
+                    RollbackInfo info = rollback.info;
+                    ipw.println(info.getRollbackId() + ":");
                     ipw.increaseIndent();
-                    for (VersionedPackage cPkg : info.getCausePackages()) {
-                        ipw.println(cPkg.getPackageName() + " " + cPkg.getLongVersionCode());
+                    ipw.println("-state: " + rollback.getStateAsString());
+                    ipw.println("-timestamp: " + rollback.getTimestamp());
+                    if (rollback.getStagedSessionId() != -1) {
+                        ipw.println("-stagedSessionId: " + rollback.getStagedSessionId());
+                    }
+                    ipw.println("-packages:");
+                    ipw.increaseIndent();
+                    for (PackageRollbackInfo pkg : info.getPackages()) {
+                        ipw.println(pkg.getPackageName()
+                                + " " + pkg.getVersionRolledBackFrom().getLongVersionCode()
+                                + " -> " + pkg.getVersionRolledBackTo().getLongVersionCode());
                     }
                     ipw.decreaseIndent();
-                    ipw.println("-committedSessionId: " + info.getCommittedSessionId());
+                    if (rollback.isCommitted()) {
+                        ipw.println("-causePackages:");
+                        ipw.increaseIndent();
+                        for (VersionedPackage cPkg : info.getCausePackages()) {
+                            ipw.println(cPkg.getPackageName() + " " + cPkg.getLongVersionCode());
+                        }
+                        ipw.decreaseIndent();
+                        ipw.println("-committedSessionId: " + info.getCommittedSessionId());
+                    }
+                    ipw.decreaseIndent();
                 }
-                ipw.decreaseIndent();
             }
         }
     }
@@ -1516,7 +1549,8 @@
         }
     }
 
-    NewRollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) {
+    @GuardedBy("mLock")
+    private NewRollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) {
         int rollbackId = allocateRollbackIdLocked();
         final Rollback rollback;
         int parentSessionId = parentSession.getSessionId();
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 772c53f..b6d1f18 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -27,6 +27,8 @@
 import android.util.Slog;
 import android.util.SparseLongArray;
 
+import com.android.internal.annotations.GuardedBy;
+
 import libcore.io.IoUtils;
 
 import org.json.JSONArray;
@@ -250,6 +252,7 @@
     /**
      * Saves the given rollback to persistent storage.
      */
+    @GuardedBy("rollback.getLock")
     void saveRollback(Rollback rollback) throws IOException {
         try {
             JSONObject dataJson = new JSONObject();
diff --git a/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java
index 3076284..d49b958 100644
--- a/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java
+++ b/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java
@@ -34,6 +34,12 @@
 
     private static final Pattern RSS_HIGH_WATER_MARK_IN_KILOBYTES =
             Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB");
+    private static final Pattern RSS_IN_KILOBYTES =
+            Pattern.compile("VmRSS:\\s*(\\d+)\\s*kB");
+    private static final Pattern ANON_RSS_IN_KILOBYTES =
+            Pattern.compile("RssAnon:\\s*(\\d+)\\s*kB");
+    private static final Pattern SWAP_IN_KILOBYTES =
+            Pattern.compile("VmSwap:\\s*(\\d+)\\s*kB");
 
     private ProcfsMemoryUtil() {}
 
@@ -52,16 +58,25 @@
      */
     @VisibleForTesting
     static int parseVmHWMFromStatus(String contents) {
-        if (contents.isEmpty()) {
-            return 0;
-        }
-        final Matcher matcher = RSS_HIGH_WATER_MARK_IN_KILOBYTES.matcher(contents);
-        try {
-            return matcher.find() ? Integer.parseInt(matcher.group(1)) : 0;
-        } catch (NumberFormatException e) {
-            Slog.e(TAG, "Failed to parse value", e);
-            return 0;
-        }
+        return tryParseInt(contents, RSS_HIGH_WATER_MARK_IN_KILOBYTES);
+    }
+
+    /**
+     * Reads memory stat of a process from procfs. Returns values of the VmRss, AnonRSS, VmSwap
+     * fields in /proc/pid/status in kilobytes or 0 if not available.
+     */
+    static MemorySnapshot readMemorySnapshotFromProcfs(int pid) {
+        final String statusPath = String.format(Locale.US, STATUS_FILE_FMT, pid);
+        return parseMemorySnapshotFromStatus(readFile(statusPath));
+    }
+
+    @VisibleForTesting
+    static MemorySnapshot parseMemorySnapshotFromStatus(String contents) {
+        final MemorySnapshot snapshot = new MemorySnapshot();
+        snapshot.rssInKilobytes = tryParseInt(contents, RSS_IN_KILOBYTES);
+        snapshot.anonRssInKilobytes = tryParseInt(contents, ANON_RSS_IN_KILOBYTES);
+        snapshot.swapInKilobytes = tryParseInt(contents, SWAP_IN_KILOBYTES);
+        return snapshot;
     }
 
     private static String readFile(String path) {
@@ -72,4 +87,27 @@
             return "";
         }
     }
+
+    private static int tryParseInt(String contents, Pattern pattern) {
+        if (contents.isEmpty()) {
+            return 0;
+        }
+        final Matcher matcher = pattern.matcher(contents);
+        try {
+            return matcher.find() ? Integer.parseInt(matcher.group(1)) : 0;
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Failed to parse value", e);
+            return 0;
+        }
+    }
+
+    static final class MemorySnapshot {
+        public int rssInKilobytes;
+        public int anonRssInKilobytes;
+        public int swapInKilobytes;
+
+        boolean isEmpty() {
+            return (anonRssInKilobytes + swapInKilobytes) == 0;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 19b8055..e1a48ed 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -29,6 +29,7 @@
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromProcfs;
 import static com.android.server.stats.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
 import static com.android.server.stats.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
+import static com.android.server.stats.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
 import static com.android.server.stats.ProcfsMemoryUtil.readRssHighWaterMarkFromProcfs;
 
 import android.annotation.NonNull;
@@ -141,6 +142,7 @@
 import com.android.server.am.MemoryStatUtil.MemoryStat;
 import com.android.server.role.RoleManagerInternal;
 import com.android.server.stats.IonMemoryUtil.IonAllocations;
+import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot;
 import com.android.server.storage.DiskStatsFileLogger;
 import com.android.server.storage.DiskStatsLoggingService;
 
@@ -1270,6 +1272,47 @@
         SystemProperties.set("sys.rss_hwm_reset.on", "1");
     }
 
+    private void pullProcessMemorySnapshot(
+            int tagId, long elapsedNanos, long wallClockNanos,
+            List<StatsLogEventWrapper> pulledData) {
+        List<ProcessMemoryState> managedProcessList =
+                LocalServices.getService(
+                        ActivityManagerInternal.class).getMemoryStateForProcesses();
+        for (ProcessMemoryState managedProcess : managedProcessList) {
+            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+            e.writeInt(managedProcess.uid);
+            e.writeString(managedProcess.processName);
+            e.writeInt(managedProcess.pid);
+            e.writeInt(managedProcess.oomScore);
+            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid);
+            if (snapshot.isEmpty()) {
+                continue;
+            }
+            e.writeInt(snapshot.rssInKilobytes);
+            e.writeInt(snapshot.anonRssInKilobytes);
+            e.writeInt(snapshot.swapInKilobytes);
+            e.writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes);
+            pulledData.add(e);
+        }
+        int[] pids = getPidsForCommands(MEMORY_INTERESTING_NATIVE_PROCESSES);
+        for (int pid : pids) {
+            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+            e.writeInt(getUidForPid(pid));
+            e.writeString(readCmdlineFromProcfs(pid));
+            e.writeInt(pid);
+            e.writeInt(-1001);  // Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.
+            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid);
+            if (snapshot.isEmpty()) {
+                continue;
+            }
+            e.writeInt(snapshot.rssInKilobytes);
+            e.writeInt(snapshot.anonRssInKilobytes);
+            e.writeInt(snapshot.swapInKilobytes);
+            e.writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes);
+            pulledData.add(e);
+        }
+    }
+
     private void pullSystemIonHeapSize(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
@@ -2352,6 +2395,10 @@
                 pullProcessMemoryHighWaterMark(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+            case StatsLog.PROCESS_MEMORY_SNAPSHOT: {
+                pullProcessMemorySnapshot(tagId, elapsedNanos, wallClockNanos, ret);
+                break;
+            }
             case StatsLog.SYSTEM_ION_HEAP_SIZE: {
                 pullSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 6830ade..ec36a82 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -181,9 +181,8 @@
      */
     @Override
     public long interceptKeyBeforeDispatching(
-            IBinder focus, KeyEvent event, int policyFlags) {
-        WindowState windowState = mService.windowForClientLocked(null, focus, false);
-        return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);
+            IBinder focusedToken, KeyEvent event, int policyFlags) {
+        return mService.mPolicy.interceptKeyBeforeDispatching(focusedToken, event, policyFlags);
     }
 
     /**
@@ -192,9 +191,8 @@
      */
     @Override
     public KeyEvent dispatchUnhandledKey(
-            IBinder focus, KeyEvent event, int policyFlags) {
-        WindowState windowState = mService.windowForClientLocked(null, focus, false);
-        return mService.mPolicy.dispatchUnhandledKey(windowState, event, policyFlags);
+            IBinder focusedToken, KeyEvent event, int policyFlags) {
+        return mService.mPolicy.dispatchUnhandledKey(focusedToken, event, policyFlags);
     }
 
     /** Callback to get pointer layer. */
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index dd9000e..8e0531c 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -528,6 +528,10 @@
             populateInputWindowHandle(
                     inputWindowHandle, w, flags, type, isVisible, hasFocus, hasWallpaper);
 
+            // register key interception info
+            mService.mKeyInterceptionInfoForToken.put(inputWindowHandle.token,
+                    w.getKeyInterceptionInfo());
+
             if (w.mWinAnimator.hasSurface()) {
                 mInputTransaction.setInputWindowInfo(
                         w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 85ba806..5e14087 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -611,7 +611,7 @@
 
     @Override
     public SurfaceControl getAnimationLeashParent() {
-        if (!WindowManagerService.sHierarchicalAnimations) {
+        if (WindowManagerService.sHierarchicalAnimations) {
             return super.getAnimationLeashParent();
         }
         // Currently, only the recents animation will create animation leashes for tasks. In this
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 750926f..aa2a964 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -30,6 +30,7 @@
 import android.view.MagnificationSpec;
 import android.view.WindowInfo;
 
+import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.server.input.InputManagerService;
 import com.android.server.policy.WindowManagerPolicy;
 
@@ -519,4 +520,10 @@
      */
     public abstract boolean isTouchableDisplay(int displayId);
 
+    /**
+     * Returns the info associated with the input token used to determine if a key should be
+     * intercepted. This info can be accessed without holding the global wm lock.
+     */
+    public abstract @Nullable KeyInterceptionInfo
+            getKeyInterceptionInfoFromToken(IBinder inputToken);
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 14214b4..d2d6902 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -248,6 +248,7 @@
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IShortcutService;
+import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.LatencyTracker;
@@ -284,8 +285,10 @@
 import java.text.DateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
@@ -407,6 +410,14 @@
     int mVr2dDisplayId = INVALID_DISPLAY;
     boolean mVrModeEnabled = false;
 
+    /**
+     * Tracks a map of input tokens to info that is used to decide whether to intercept
+     * a key event.
+     */
+    final Map<IBinder, KeyInterceptionInfo> mKeyInterceptionInfoForToken =
+            Collections.synchronizedMap(new ArrayMap<>());
+
+
     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
         @Override
         public void onVrStateChanged(boolean enabled) {
@@ -7360,6 +7371,11 @@
                         && configuration.touchscreen == Configuration.TOUCHSCREEN_FINGER;
             }
         }
+
+        @Override
+        public @Nullable KeyInterceptionInfo getKeyInterceptionInfoFromToken(IBinder inputToken) {
+            return mKeyInterceptionInfoForToken.get(inputToken);
+        }
     }
 
     void registerAppFreezeListener(AppFreezeListener listener) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 501a93e..0a65e324 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -203,6 +203,7 @@
 import android.view.animation.Interpolator;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
@@ -633,6 +634,7 @@
     private @Nullable InsetsSourceProvider mInsetProvider;
 
     private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
+    private KeyInterceptionInfo mKeyInterceptionInfo;
 
     void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation,
             @Rotation int rotation, boolean requested) {
@@ -2218,6 +2220,7 @@
             mClientChannel.dispose();
             mClientChannel = null;
         }
+        mWmService.mKeyInterceptionInfoForToken.remove(mInputWindowHandle.token);
         mInputWindowHandle.token = null;
     }
 
@@ -5327,4 +5330,15 @@
             proto.end(token);
         }
     }
+
+    KeyInterceptionInfo getKeyInterceptionInfo() {
+        if (mKeyInterceptionInfo == null
+                || mKeyInterceptionInfo.layoutParamsPrivateFlags != getAttrs().privateFlags
+                || mKeyInterceptionInfo.layoutParamsType != getAttrs().type
+                || mKeyInterceptionInfo.windowTitle != getWindowTag()) {
+            mKeyInterceptionInfo = new KeyInterceptionInfo(getAttrs().type, getAttrs().privateFlags,
+                    getWindowTag().toString());
+        }
+        return mKeyInterceptionInfo;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 704c808..555968a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7389,8 +7389,7 @@
 
         final long callingIdentity = mInjector.binderClearCallingIdentity();
         try {
-            mInjector.getIActivityManager().requestBugReport(
-                    ActivityManager.BUGREPORT_OPTION_REMOTE);
+            mInjector.getIActivityManager().requestRemoteBugReport();
 
             mRemoteBugreportServiceIsActive.set(true);
             mRemoteBugreportSharingAccepted.set(false);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
index 9926a09..322653b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
@@ -28,12 +28,12 @@
 import static org.mockito.Mockito.when;
 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
 
+import android.os.IBinder;
 import android.view.KeyEvent;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.policy.WindowManagerPolicy.WindowState;
 
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
@@ -79,7 +79,7 @@
     @Test
     public void whenVolumeKeyArrives_andPolicySaysUseIt_eventGoesToAms() {
         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(event)), eq(0))).thenReturn(0L);
         mInterceptor.onKeyEvent(event, 0);
         verify(mMockAms).notifyKeyEvent(argThat(matchesKeyEvent(event)), eq(0));
@@ -88,7 +88,7 @@
     @Test
     public void whenVolumeKeyArrives_andPolicySaysDropIt_eventDropped() {
         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(event)), eq(0))).thenReturn(-1L);
         mInterceptor.onKeyEvent(event, 0);
         verify(mMockAms, times(0)).notifyKeyEvent(anyObject(), anyInt());
@@ -98,14 +98,14 @@
     @Test
     public void whenVolumeKeyArrives_andPolicySaysDelayThenUse_eventQueuedThenSentToAms() {
         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(event)), eq(0))).thenReturn(150L);
         mInterceptor.onKeyEvent(event, 0);
 
         assertTrue(mHandler.hasMessages());
         verify(mMockAms, times(0)).notifyKeyEvent(anyObject(), anyInt());
 
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(event)), eq(0))).thenReturn(0L);
         mHandler.sendAllMessages();
 
@@ -115,14 +115,14 @@
     @Test
     public void whenVolumeKeyArrives_andPolicySaysDelayThenDrop_eventQueuedThenDropped() {
         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(event)), eq(0))).thenReturn(150L);
         mInterceptor.onKeyEvent(event, 0);
 
         assertTrue(mHandler.hasMessages());
         verify(mMockAms, times(0)).notifyKeyEvent(anyObject(), anyInt());
 
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(event)), eq(0))).thenReturn(-1L);
         mHandler.sendAllMessages();
 
@@ -137,18 +137,18 @@
                 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP),
                 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_0)};
 
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[1])), eq(0))).thenReturn(150L);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[3])), eq(0))).thenReturn(75L);
 
         for (KeyEvent event : events) {
             mInterceptor.onKeyEvent(event, 0);
         }
 
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[1])), eq(0))).thenReturn(0L);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[3])), eq(0))).thenReturn(0L);
 
         mHandler.sendAllMessages();
@@ -167,18 +167,18 @@
                 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP),
                 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_0)};
 
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[1])), eq(0))).thenReturn(150L);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[3])), eq(0))).thenReturn(75L);
 
         for (KeyEvent event : events) {
             mInterceptor.onKeyEvent(event, 0);
         }
 
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[1])), eq(0))).thenReturn(-1L);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[3])), eq(0))).thenReturn(-1L);
 
         mHandler.sendAllMessages();
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index 29a8dad..5c2ad94 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -126,22 +126,6 @@
         doTestConnectionDisconnectionReconnection(AudioService.BECOMING_NOISY_DELAY_MS / 2);
     }
 
-    /**
-     * Verify connecting an A2DP sink will call into AudioService to unmute media
-     */
-    @Test
-    public void testA2dpConnectionUnmutesMedia() throws Exception {
-        Log.i(TAG, "testA2dpConnectionUnmutesMedia");
-        Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
-
-        mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
-                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1);
-        Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
-        verify(mMockAudioService, times(1)).postAccessoryPlugMediaUnmute(
-                ArgumentMatchers.eq(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
-
-    }
-
     private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection)
             throws Exception {
         when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC))
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index fc7cfec..0a310d1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -118,17 +118,20 @@
         String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" };
         String[] appdir = { "app", "priv-app" };
         for (int i = 0; i < partitions.length; i++) {
+            final PackageManagerService.SystemPartition systemPartition =
+                    PackageManagerService.SYSTEM_PARTITIONS.get(i);
             for (int j = 0; j < appdir.length; j++) {
                 String canonical = new File("/" + partitions[i]).getCanonicalPath();
                 String path = String.format("%s/%s/A.apk", canonical, appdir[j]);
 
-                Assert.assertEquals(j == 1 && i != 3,
-                    PackageManagerService.locationIsPrivileged(path));
+                Assert.assertEquals(j == 1 && i != 3, systemPartition.containsPrivPath(path));
 
-                Assert.assertEquals(i == 1 || i == 2, PackageManagerService.locationIsVendor(path));
-                Assert.assertEquals(i == 3, PackageManagerService.locationIsOem(path));
-                Assert.assertEquals(i == 4, PackageManagerService.locationIsProduct(path));
-                Assert.assertEquals(i == 5, PackageManagerService.locationIsSystemExt(path));
+                final int scanFlag = systemPartition.scanFlag;
+                Assert.assertEquals(i == 1, scanFlag == PackageManagerService.SCAN_AS_VENDOR);
+                Assert.assertEquals(i == 2, scanFlag == PackageManagerService.SCAN_AS_ODM);
+                Assert.assertEquals(i == 3, scanFlag == PackageManagerService.SCAN_AS_OEM);
+                Assert.assertEquals(i == 4, scanFlag == PackageManagerService.SCAN_AS_PRODUCT);
+                Assert.assertEquals(i == 5, scanFlag == PackageManagerService.SCAN_AS_SYSTEM_EXT);
             }
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
index 8cb5197..0b8c2a55 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
@@ -45,8 +45,6 @@
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Set;
 
 @RunWith(JUnit4.class)
 public class AppDataRollbackHelperTest {
@@ -250,28 +248,22 @@
         dataForRestore.info.getPackages().add(pendingRestore);
         dataForRestore.info.getPackages().add(wasRecentlyRestored);
 
-        Set<Rollback> changed = helper.commitPendingBackupAndRestoreForUser(37,
-                Arrays.asList(dataWithPendingBackup, dataWithRecentRestore, dataForDifferentUser,
-                    dataForRestore));
         InOrder inOrder = Mockito.inOrder(installer);
 
         // Check that pending backup and restore for the same package mutually destroyed each other.
+        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataWithRecentRestore));
         assertEquals(-1, wasRecentlyRestored.getPendingBackups().indexOf(37));
         assertNull(wasRecentlyRestored.getRestoreInfo(37));
 
         // Check that backup was performed.
+        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataWithPendingBackup));
         inOrder.verify(installer).snapshotAppData(eq("com.foo"), eq(37), eq(101),
                 eq(Installer.FLAG_STORAGE_CE));
         assertEquals(-1, pendingBackup.getPendingBackups().indexOf(37));
         assertEquals(53, pendingBackup.getCeSnapshotInodes().get(37));
 
-        // Check that changed returns correct Rollback.
-        assertEquals(3, changed.size());
-        assertTrue(changed.contains(dataWithPendingBackup));
-        assertTrue(changed.contains(dataWithRecentRestore));
-        assertTrue(changed.contains(dataForRestore));
-
         // Check that restore was performed.
+        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataForRestore));
         inOrder.verify(installer).restoreAppDataSnapshot(
                 eq("com.abc"), eq(57) /* appId */, eq("seInfo"), eq(37) /* userId */,
                 eq(17239) /* rollbackId */, eq(Installer.FLAG_STORAGE_CE));
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
index d27f1c7..b5925a6 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -18,11 +18,18 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.content.pm.VersionedPackage;
+import android.content.rollback.PackageRollbackInfo;
+import android.util.IntArray;
+import android.util.SparseLongArray;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
 
 @RunWith(JUnit4.class)
 public class RollbackUnitTest {
@@ -74,4 +81,62 @@
         assertThat(rollback.isCommitted()).isTrue();
     }
 
+    @Test
+    public void getPackageNamesAllAndJustApex() {
+        String pkg1 = "test.testpackage.pkg1";
+        String pkg2 = "test.testpackage.pkg2";
+        String pkg3 = "com.blah.hello.three";
+        String pkg4 = "com.something.4pack";
+
+        Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+        PackageRollbackInfo pkgInfo1 = pkgInfoFor(pkg1, 12, 10, false);
+        PackageRollbackInfo pkgInfo2 = pkgInfoFor(pkg2, 12, 10, true);
+        PackageRollbackInfo pkgInfo3 = pkgInfoFor(pkg3, 12, 10, false);
+        PackageRollbackInfo pkgInfo4 = pkgInfoFor(pkg4, 12, 10, true);
+
+        rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2, pkgInfo3, pkgInfo4));
+
+        assertThat(rollback.getPackageNames()).containsExactly(pkg1, pkg2, pkg3, pkg4);
+        assertThat(rollback.getApexPackageNames()).containsExactly(pkg2, pkg4);
+    }
+
+    @Test
+    public void includesPackages() {
+        String pkg1 = "test.testpackage.pkg1";
+        String pkg2 = "test.testpackage.pkg2";
+        String pkg3 = "com.blah.hello.three";
+        String pkg4 = "com.something.4pack";
+
+        Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+        PackageRollbackInfo pkgInfo1 = pkgInfoFor(pkg1, 12, 10, false);
+        PackageRollbackInfo pkgInfo2 = pkgInfoFor(pkg2, 18, 12, true);
+        PackageRollbackInfo pkgInfo3 = pkgInfoFor(pkg3, 157, 156, false);
+        PackageRollbackInfo pkgInfo4 = pkgInfoFor(pkg4, 99, 1, true);
+
+        rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2, pkgInfo3, pkgInfo4));
+
+        assertThat(rollback.includesPackage(pkg2)).isTrue();
+        assertThat(rollback.includesPackage(pkg3)).isTrue();
+        assertThat(rollback.includesPackage("com.something.else")).isFalse();
+
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg1, 12)).isFalse();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg1, 1)).isTrue();
+
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg2, 18)).isFalse();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg2, 12)).isTrue();
+
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg3, 157)).isFalse();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg3, 156)).isTrue();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg3, 15)).isTrue();
+
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg4, 99)).isFalse();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg4, 100)).isTrue();
+    }
+
+    private static PackageRollbackInfo pkgInfoFor(
+            String packageName, long fromVersion, long toVersion, boolean isApex) {
+        return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion),
+                new VersionedPackage(packageName, toVersion),
+                new IntArray(), new ArrayList<>(), isApex, new IntArray(), new SparseLongArray());
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java
index 4fb533f..ae57774 100644
--- a/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java
+++ b/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java
@@ -15,12 +15,15 @@
  */
 package com.android.server.stats;
 
+import static com.android.server.stats.ProcfsMemoryUtil.parseMemorySnapshotFromStatus;
 import static com.android.server.stats.ProcfsMemoryUtil.parseVmHWMFromStatus;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot;
+
 import org.junit.Test;
 
 /**
@@ -90,4 +93,32 @@
     public void testParseVmHWMFromStatus_emptyContents() {
         assertThat(parseVmHWMFromStatus("")).isEqualTo(0);
     }
+
+    @Test
+    public void testParseMemorySnapshotFromStatus_parsesCorrectValue() {
+        MemorySnapshot snapshot = parseMemorySnapshotFromStatus(STATUS_CONTENTS);
+        assertThat(snapshot.rssInKilobytes).isEqualTo(126776);
+        assertThat(snapshot.anonRssInKilobytes).isEqualTo(37860);
+        assertThat(snapshot.swapInKilobytes).isEqualTo(22);
+        assertThat(snapshot.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void testParseMemorySnapshotFromStatus_invalidValue() {
+        MemorySnapshot snapshot =
+                parseMemorySnapshotFromStatus("test\nVmRSS:\tx0x0x\nVmSwap:\t1 kB\ntest");
+        assertThat(snapshot.rssInKilobytes).isEqualTo(0);
+        assertThat(snapshot.anonRssInKilobytes).isEqualTo(0);
+        assertThat(snapshot.swapInKilobytes).isEqualTo(1);
+        assertThat(snapshot.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void testParseMemorySnapshotFromStatus_emptyContents() {
+        MemorySnapshot snapshot = parseMemorySnapshotFromStatus("");
+        assertThat(snapshot.rssInKilobytes).isEqualTo(0);
+        assertThat(snapshot.anonRssInKilobytes).isEqualTo(0);
+        assertThat(snapshot.swapInKilobytes).isEqualTo(0);
+        assertThat(snapshot.isEmpty()).isTrue();
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index bb89446..761f73e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -167,12 +167,13 @@
     }
 
     @Override
-    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
+    public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
+            int policyFlags) {
         return 0;
     }
 
     @Override
-    public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
+    public KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags) {
         return null;
     }
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 090623e..e3183e3 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -340,6 +340,10 @@
             mUserUnlockedStates.put(userId, true);
             final UserUsageStatsService userService = getUserDataAndInitializeIfNeededLocked(
                     userId, System.currentTimeMillis());
+            if (userService == null) {
+                Slog.i(TAG, "Attempted to unlock stopped or removed user " + userId);
+                return;
+            }
             userService.userUnlocked(System.currentTimeMillis());
             // Process all the pending reported events
             while (pendingEvents.peek() != null) {
@@ -456,7 +460,17 @@
                     "usagestats");
             service = new UserUsageStatsService(getContext(), userId, usageStatsDir, this);
             if (mUserUnlockedStates.get(userId)) {
-                service.init(currentTimeMillis);
+                try {
+                    service.init(currentTimeMillis);
+                } catch (Exception e) {
+                    if (mUserManager.isUserUnlocked(userId)) {
+                        throw e; // rethrow exception - user is unlocked
+                    } else {
+                        Slog.w(TAG, "Attempted to initialize service for "
+                                + "stopped or removed user " + userId);
+                        return null;
+                    }
+                }
             }
             mUserState.put(userId, service);
         }
@@ -779,6 +793,9 @@
 
             final UserUsageStatsService service =
                     getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+            if (service == null) {
+                return; // user was stopped or removed
+            }
             service.reportEvent(event);
 
             mAppStandby.reportEvent(event, elapsedRealtime, userId);
@@ -841,6 +858,9 @@
 
             final UserUsageStatsService service =
                     getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
+            if (service == null) {
+                return null; // user was stopped or removed
+            }
             List<UsageStats> list = service.queryUsageStats(bucketType, beginTime, endTime);
             if (list == null) {
                 return null;
@@ -873,6 +893,9 @@
 
             final UserUsageStatsService service =
                     getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
+            if (service == null) {
+                return null; // user was stopped or removed
+            }
             return service.queryConfigurationStats(bucketType, beginTime, endTime);
         }
     }
@@ -890,6 +913,9 @@
 
             final UserUsageStatsService service =
                     getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
+            if (service == null) {
+                return null; // user was stopped or removed
+            }
             return service.queryEventStats(bucketType, beginTime, endTime);
         }
     }
@@ -907,6 +933,9 @@
 
             final UserUsageStatsService service =
                     getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
+            if (service == null) {
+                return null; // user was stopped or removed
+            }
             return service.queryEvents(beginTime, endTime, shouldObfuscateInstantApps);
         }
     }
@@ -924,6 +953,9 @@
 
             final UserUsageStatsService service =
                     getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
+            if (service == null) {
+                return null; // user was stopped or removed
+            }
             return service.queryEventsForPackage(beginTime, endTime, packageName, includeTaskRoot);
         }
     }
@@ -1113,7 +1145,15 @@
                     flushToDisk();
                     break;
                 case MSG_UNLOCKED_USER:
-                    onUserUnlocked(msg.arg1);
+                    try {
+                        onUserUnlocked(msg.arg1);
+                    } catch (Exception e) {
+                        if (mUserManager.isUserUnlocked(msg.arg1)) {
+                            throw e; // rethrow exception - user is unlocked
+                        } else {
+                            Slog.w(TAG, "Attempted to unlock stopped or removed user " + msg.arg1);
+                        }
+                    }
                     break;
                 case MSG_REMOVE_USER:
                     onUserRemoved(msg.arg1);
@@ -1986,6 +2026,9 @@
                 if (user == UserHandle.USER_SYSTEM) {
                     final UserUsageStatsService userStats = getUserDataAndInitializeIfNeededLocked(
                             user, System.currentTimeMillis());
+                    if (userStats == null) {
+                        return null; // user was stopped or removed
+                    }
                     return userStats.getBackupPayload(key);
                 } else {
                     return null;
@@ -2004,6 +2047,9 @@
                 if (user == UserHandle.USER_SYSTEM) {
                     final UserUsageStatsService userStats = getUserDataAndInitializeIfNeededLocked(
                             user, System.currentTimeMillis());
+                    if (userStats == null) {
+                        return; // user was stopped or removed
+                    }
                     userStats.applyRestoredPayload(key, payload);
                 }
             }
diff --git a/services/wifi/Android.bp b/services/wifi/Android.bp
index 3c916a6..608fc2c 100644
--- a/services/wifi/Android.bp
+++ b/services/wifi/Android.bp
@@ -5,6 +5,9 @@
         "java/**/*.java",
         "java/**/*.aidl",
     ],
+    aidl: {
+        local_include_dirs: ["java"]
+    },
     libs: [
         "services.net",
     ],
diff --git a/services/wifi/java/android/net/wifi/IWifiStackConnector.aidl b/services/wifi/java/android/net/wifi/IWifiStackConnector.aidl
index eadc726..3af4666 100644
--- a/services/wifi/java/android/net/wifi/IWifiStackConnector.aidl
+++ b/services/wifi/java/android/net/wifi/IWifiStackConnector.aidl
@@ -15,8 +15,9 @@
  */
 package android.net.wifi;
 
+import android.net.wifi.WifiApiServiceInfo;
+
 /** @hide */
 interface IWifiStackConnector {
-     IBinder retrieveApiServiceImpl(String serviceName);
-     boolean startApiService(String serviceName);
+     List<WifiApiServiceInfo> getWifiApiServiceInfos();
 }
diff --git a/services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl b/services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl
new file mode 100644
index 0000000..45e4c69
--- /dev/null
+++ b/services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.net.wifi;
+
+/** @hide */
+parcelable WifiApiServiceInfo {
+    String name;
+    IBinder binder;
+}
diff --git a/services/wifi/java/android/net/wifi/WifiStackClient.java b/services/wifi/java/android/net/wifi/WifiStackClient.java
index 64af7a8..dcdfbc5 100644
--- a/services/wifi/java/android/net/wifi/WifiStackClient.java
+++ b/services/wifi/java/android/net/wifi/WifiStackClient.java
@@ -21,12 +21,13 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.net.ConnectivityModuleConnector;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
 
+import java.util.List;
+
 /**
  * Service used to communicate with the wifi stack, which could be running in a separate
  * module.
@@ -56,21 +57,23 @@
         @Override
         public void onModuleServiceConnected(IBinder service) {
             Log.i(TAG, "Wifi stack connected");
+            registerWifiStackService(service);
 
-            // spin up a new thread to not block system_server main thread
-            HandlerThread thread = new HandlerThread("InitWifiServicesThread");
-            thread.start();
-            thread.getThreadHandler().post(() -> {
-                registerWifiStackService(service);
-                IWifiStackConnector connector = IWifiStackConnector.Stub.asInterface(service);
-                registerApiServiceAndStart(connector, Context.WIFI_SCANNING_SERVICE);
-                registerApiServiceAndStart(connector, Context.WIFI_SERVICE);
-                registerApiServiceAndStart(connector, Context.WIFI_P2P_SERVICE);
-                registerApiServiceAndStart(connector, Context.WIFI_AWARE_SERVICE);
-                registerApiServiceAndStart(connector, Context.WIFI_RTT_RANGING_SERVICE);
+            IWifiStackConnector connector = IWifiStackConnector.Stub.asInterface(service);
 
-                thread.quitSafely();
-            });
+            List<WifiApiServiceInfo> wifiApiServiceInfos;
+            try {
+                wifiApiServiceInfos = connector.getWifiApiServiceInfos();
+            } catch (RemoteException e) {
+                throw new RuntimeException("Failed to getWifiApiServiceInfos()", e);
+            }
+
+            for (WifiApiServiceInfo wifiApiServiceInfo : wifiApiServiceInfos) {
+                String serviceName = wifiApiServiceInfo.name;
+                IBinder binder = wifiApiServiceInfo.binder;
+                Log.i(TAG, "Registering " + serviceName);
+                ServiceManager.addService(serviceName, binder);
+            }
         }
     }
 
@@ -81,32 +84,6 @@
         Log.i(TAG, "Wifi stack service registered");
     }
 
-    private void registerApiServiceAndStart(
-            IWifiStackConnector stackConnector, String serviceName) {
-        IBinder service = null;
-        try {
-            service = stackConnector.retrieveApiServiceImpl(serviceName);
-        } catch (RemoteException e) {
-            throw new RuntimeException("Failed to retrieve service impl " + serviceName, e);
-        }
-        if (service == null) {
-            Log.i(TAG, "Service " + serviceName + " not available");
-            return;
-        }
-        Log.i(TAG, "Registering " + serviceName);
-        ServiceManager.addService(serviceName, service);
-
-        boolean success = false;
-        try {
-            success = stackConnector.startApiService(serviceName);
-        } catch (RemoteException e) {
-            throw new RuntimeException("Failed to start service " + serviceName, e);
-        }
-        if (!success) {
-            throw new RuntimeException("Service " + serviceName + " start failed");
-        }
-    }
-
     /**
      * Start the wifi stack. Should be called only once on device startup.
      *
diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp
index 4f6524e..c380d29 100644
--- a/startop/view_compiler/Android.bp
+++ b/startop/view_compiler/Android.bp
@@ -25,6 +25,7 @@
         "slicer",
     ],
     static_libs: [
+        "libcutils",
         "libtinyxml2",
         "liblog",
         "libutils",
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index adc9e22..81b1e49 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -19,10 +19,9 @@
 java_sdk_library {
     name: "android.test.mock",
 
-    srcs: [
-        "src/**/*.java",
-        ":framework-all-sources",
-    ],
+    srcs: ["src/**/*.java"],
+    api_srcs: [":framework-all-sources"],
+    libs: ["framework-all"],
 
     api_packages: [
         "android.test.mock",